UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 7.58 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/last-login-method/index.ts"],"sourcesContent":["import type {\n\tBetterAuthPlugin,\n\tGenericEndpointContext,\n} from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\n\n/**\n * Configuration for tracking different authentication methods\n */\nexport interface LastLoginMethodOptions {\n\t/**\n\t * Name of the cookie to store the last login method\n\t * @default \"better-auth.last_used_login_method\"\n\t */\n\tcookieName?: string | undefined;\n\t/**\n\t * Cookie expiration time in seconds\n\t * @default 2592000 (30 days)\n\t */\n\tmaxAge?: number | undefined;\n\t/**\n\t * Custom method to resolve the last login method\n\t * @param ctx - The context from the hook\n\t * @returns The last login method\n\t */\n\tcustomResolveMethod?:\n\t\t| ((ctx: GenericEndpointContext) => string | null)\n\t\t| undefined;\n\t/**\n\t * Store the last login method in the database. This will create a new field in the user table.\n\t * @default false\n\t */\n\tstoreInDatabase?: boolean | undefined;\n\t/**\n\t * Custom schema for the plugin\n\t * @default undefined\n\t */\n\tschema?:\n\t\t| {\n\t\t\t\tuser?: {\n\t\t\t\t\tlastLoginMethod?: string;\n\t\t\t\t};\n\t\t }\n\t\t| undefined;\n}\n\n/**\n * Plugin to track the last used login method\n */\nexport const lastLoginMethod = <O extends LastLoginMethodOptions>(\n\tuserConfig?: O | undefined,\n) => {\n\tconst paths = [\n\t\t\"/callback/:id\",\n\t\t\"/oauth2/callback/:providerId\",\n\t\t\"/sign-in/email\",\n\t\t\"/sign-up/email\",\n\t];\n\n\tconst defaultResolveMethod = (ctx: GenericEndpointContext) => {\n\t\tif (paths.includes(ctx.path)) {\n\t\t\treturn (\n\t\t\t\tctx.params?.id || ctx.params?.providerId || ctx.path.split(\"/\").pop()\n\t\t\t);\n\t\t}\n\t\tif (ctx.path.includes(\"siwe\")) return \"siwe\";\n\t\tif (ctx.path.includes(\"/passkey/verify-authentication\")) return \"passkey\";\n\t\treturn null;\n\t};\n\n\tconst config = {\n\t\tcookieName: \"better-auth.last_used_login_method\",\n\t\tmaxAge: 60 * 60 * 24 * 30,\n\t\t...userConfig,\n\t} satisfies LastLoginMethodOptions;\n\n\treturn {\n\t\tid: \"last-login-method\",\n\t\tinit(ctx) {\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\tdatabaseHooks: {\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\tcreate: {\n\t\t\t\t\t\t\t\tasync before(user, context) {\n\t\t\t\t\t\t\t\t\tif (!config.storeInDatabase) return;\n\t\t\t\t\t\t\t\t\tif (!context) return;\n\t\t\t\t\t\t\t\t\tconst lastUsedLoginMethod =\n\t\t\t\t\t\t\t\t\t\tconfig.customResolveMethod?.(context) ??\n\t\t\t\t\t\t\t\t\t\tdefaultResolveMethod(context);\n\t\t\t\t\t\t\t\t\tif (lastUsedLoginMethod) {\n\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\t\t\t...user,\n\t\t\t\t\t\t\t\t\t\t\t\tlastLoginMethod: lastUsedLoginMethod,\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},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsession: {\n\t\t\t\t\t\t\tcreate: {\n\t\t\t\t\t\t\t\tasync after(session, context) {\n\t\t\t\t\t\t\t\t\tif (!config.storeInDatabase) return;\n\t\t\t\t\t\t\t\t\tif (!context) return;\n\t\t\t\t\t\t\t\t\tconst lastUsedLoginMethod =\n\t\t\t\t\t\t\t\t\t\tconfig.customResolveMethod?.(context) ??\n\t\t\t\t\t\t\t\t\t\tdefaultResolveMethod(context);\n\t\t\t\t\t\t\t\t\tif (lastUsedLoginMethod && session?.userId) {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tawait ctx.internalAdapter.updateUser(session.userId, {\n\t\t\t\t\t\t\t\t\t\t\t\tlastLoginMethod: lastUsedLoginMethod,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\tctx.logger.error(\n\t\t\t\t\t\t\t\t\t\t\t\t\"Failed to update lastLoginMethod\",\n\t\t\t\t\t\t\t\t\t\t\t\terror,\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},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher() {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst lastUsedLoginMethod =\n\t\t\t\t\t\t\tconfig.customResolveMethod?.(ctx) ?? defaultResolveMethod(ctx);\n\t\t\t\t\t\tif (lastUsedLoginMethod) {\n\t\t\t\t\t\t\tconst setCookie = ctx.context.responseHeaders?.get(\"set-cookie\");\n\t\t\t\t\t\t\tconst sessionTokenName =\n\t\t\t\t\t\t\t\tctx.context.authCookies.sessionToken.name;\n\t\t\t\t\t\t\tconst hasSessionToken =\n\t\t\t\t\t\t\t\tsetCookie && setCookie.includes(sessionTokenName);\n\t\t\t\t\t\t\tif (hasSessionToken) {\n\t\t\t\t\t\t\t\t// Inherit cookie attributes from Better Auth's centralized cookie system\n\t\t\t\t\t\t\t\t// This ensures consistency with cross-origin, cross-subdomain, and security settings\n\t\t\t\t\t\t\t\tconst cookieAttributes = {\n\t\t\t\t\t\t\t\t\t...ctx.context.authCookies.sessionToken.options,\n\t\t\t\t\t\t\t\t\tmaxAge: config.maxAge,\n\t\t\t\t\t\t\t\t\thttpOnly: false, // Override: plugin cookies are not httpOnly\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\tctx.setCookie(\n\t\t\t\t\t\t\t\t\tconfig.cookieName,\n\t\t\t\t\t\t\t\t\tlastUsedLoginMethod,\n\t\t\t\t\t\t\t\t\tcookieAttributes,\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],\n\t\t},\n\t\tschema: (config.storeInDatabase\n\t\t\t? {\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tfields: {\n\t\t\t\t\t\t\tlastLoginMethod: {\n\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\tinput: false,\n\t\t\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\t\t\tfieldName:\n\t\t\t\t\t\t\t\t\tconfig.schema?.user?.lastLoginMethod || \"lastLoginMethod\",\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: undefined) as O[\"storeInDatabase\"] extends true\n\t\t\t? {\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tfields: {\n\t\t\t\t\t\t\tlastLoginMethod: {\n\t\t\t\t\t\t\t\ttype: \"string\";\n\t\t\t\t\t\t\t\trequired: false;\n\t\t\t\t\t\t\t\tinput: false;\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: undefined,\n\t\toptions: userConfig as NoInfer<O>,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":";;;;;;AAiDA,MAAa,mBACZ,eACI;CACJ,MAAM,QAAQ;EACb;EACA;EACA;EACA;EACA;CAED,MAAM,wBAAwB,QAAgC;AAC7D,MAAI,MAAM,SAAS,IAAI,KAAK,CAC3B,QACC,IAAI,QAAQ,MAAM,IAAI,QAAQ,cAAc,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK;AAGvE,MAAI,IAAI,KAAK,SAAS,OAAO,CAAE,QAAO;AACtC,MAAI,IAAI,KAAK,SAAS,iCAAiC,CAAE,QAAO;AAChE,SAAO;;CAGR,MAAM,SAAS;EACd,YAAY;EACZ,QAAQ,OAAU,KAAK;EACvB,GAAG;EACH;AAED,QAAO;EACN,IAAI;EACJ,KAAK,KAAK;AACT,UAAO,EACN,SAAS,EACR,eAAe;IACd,MAAM,EACL,QAAQ,EACP,MAAM,OAAO,MAAM,SAAS;AAC3B,SAAI,CAAC,OAAO,gBAAiB;AAC7B,SAAI,CAAC,QAAS;KACd,MAAM,sBACL,OAAO,sBAAsB,QAAQ,IACrC,qBAAqB,QAAQ;AAC9B,SAAI,oBACH,QAAO,EACN,MAAM;MACL,GAAG;MACH,iBAAiB;MACjB,EACD;OAGH,EACD;IACD,SAAS,EACR,QAAQ,EACP,MAAM,MAAM,SAAS,SAAS;AAC7B,SAAI,CAAC,OAAO,gBAAiB;AAC7B,SAAI,CAAC,QAAS;KACd,MAAM,sBACL,OAAO,sBAAsB,QAAQ,IACrC,qBAAqB,QAAQ;AAC9B,SAAI,uBAAuB,SAAS,OACnC,KAAI;AACH,YAAM,IAAI,gBAAgB,WAAW,QAAQ,QAAQ,EACpD,iBAAiB,qBACjB,CAAC;cACM,OAAO;AACf,UAAI,OAAO,MACV,oCACA,MACA;;OAIJ,EACD;IACD,EACD,EACD;;EAEF,OAAO,EACN,OAAO,CACN;GACC,UAAU;AACT,WAAO;;GAER,SAAS,qBAAqB,OAAO,QAAQ;IAC5C,MAAM,sBACL,OAAO,sBAAsB,IAAI,IAAI,qBAAqB,IAAI;AAC/D,QAAI,qBAAqB;KACxB,MAAM,YAAY,IAAI,QAAQ,iBAAiB,IAAI,aAAa;KAChE,MAAM,mBACL,IAAI,QAAQ,YAAY,aAAa;AAGtC,SADC,aAAa,UAAU,SAAS,iBAAiB,EAC7B;MAGpB,MAAM,mBAAmB;OACxB,GAAG,IAAI,QAAQ,YAAY,aAAa;OACxC,QAAQ,OAAO;OACf,UAAU;OACV;AAED,UAAI,UACH,OAAO,YACP,qBACA,iBACA;;;KAGF;GACF,CACD,EACD;EACD,QAAS,OAAO,kBACb,EACA,MAAM,EACL,QAAQ,EACP,iBAAiB;GAChB,MAAM;GACN,OAAO;GACP,UAAU;GACV,WACC,OAAO,QAAQ,MAAM,mBAAmB;GACzC,EACD,EACD,EACD,GACA;EAaH,SAAS;EACT"}