better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 8.21 kB
Source Map (JSON)
{"version":3,"file":"state.mjs","names":["stateCookie","parsedData: StateData"],"sources":["../src/state.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport * as z from \"zod\";\nimport { expireCookie } from \"./cookies\";\nimport {\n\tgenerateRandomString,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"./crypto\";\n\nconst stateDataSchema = z.looseObject({\n\tcallbackURL: z.string(),\n\tcodeVerifier: z.string(),\n\terrorURL: z.string().optional(),\n\tnewUserURL: z.string().optional(),\n\texpiresAt: z.number(),\n\tlink: z\n\t\t.object({\n\t\t\temail: z.string(),\n\t\t\tuserId: z.coerce.string(),\n\t\t})\n\t\t.optional(),\n\trequestSignUp: z.boolean().optional(),\n});\n\nexport type StateData = z.infer<typeof stateDataSchema>;\n\nexport type StateErrorCode =\n\t| \"state_generation_error\"\n\t| \"state_invalid\"\n\t| \"state_mismatch\"\n\t| \"state_security_mismatch\";\n\nexport class StateError extends BetterAuthError {\n\tcode: string;\n\tdetails?: Record<string, any>;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions: ErrorOptions & {\n\t\t\tcode: StateErrorCode;\n\t\t\tdetails?: Record<string, any>;\n\t\t},\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = options.code;\n\t\tthis.details = options.details;\n\t}\n}\n\nexport async function generateGenericState(\n\tc: GenericEndpointContext,\n\tstateData: StateData,\n\tsettings?: { cookieName: string },\n) {\n\tconst state = generateRandomString(32);\n\tconst storeStateStrategy = c.context.oauthConfig.storeStateStrategy;\n\n\tif (storeStateStrategy === \"cookie\") {\n\t\t// Store state data in an encrypted cookie\n\n\t\tconst encryptedData = await symmetricEncrypt({\n\t\t\tkey: c.context.secret,\n\t\t\tdata: JSON.stringify(stateData),\n\t\t});\n\n\t\tconst stateCookie = c.context.createAuthCookie(\n\t\t\tsettings?.cookieName ?? \"oauth_state\",\n\t\t\t{\n\t\t\t\tmaxAge: 10 * 60, // 10 minutes\n\t\t\t},\n\t\t);\n\n\t\tc.setCookie(stateCookie.name, encryptedData, stateCookie.attributes);\n\n\t\treturn {\n\t\t\tstate,\n\t\t\tcodeVerifier: stateData.codeVerifier,\n\t\t};\n\t}\n\n\t// Default: database strategy\n\n\tconst stateCookie = c.context.createAuthCookie(\n\t\tsettings?.cookieName ?? \"state\",\n\t\t{\n\t\t\tmaxAge: 5 * 60, // 5 minutes\n\t\t},\n\t);\n\n\tawait c.setSignedCookie(\n\t\tstateCookie.name,\n\t\tstate,\n\t\tc.context.secret,\n\t\tstateCookie.attributes,\n\t);\n\n\tconst expiresAt = new Date();\n\texpiresAt.setMinutes(expiresAt.getMinutes() + 10);\n\n\tconst verification = await c.context.internalAdapter.createVerificationValue({\n\t\tvalue: JSON.stringify(stateData),\n\t\tidentifier: state,\n\t\texpiresAt,\n\t});\n\n\tif (!verification) {\n\t\tthrow new StateError(\n\t\t\t\"Unable to create verification. Make sure the database adapter is properly working and there is a verification table in the database\",\n\t\t\t{\n\t\t\t\tcode: \"state_generation_error\",\n\t\t\t},\n\t\t);\n\t}\n\n\treturn {\n\t\tstate: verification.identifier,\n\t\tcodeVerifier: stateData.codeVerifier,\n\t};\n}\n\nexport async function parseGenericState(\n\tc: GenericEndpointContext,\n\tstate: string,\n\tsettings?: { cookieName: string },\n) {\n\tconst storeStateStrategy = c.context.oauthConfig.storeStateStrategy;\n\tlet parsedData: StateData;\n\n\tif (storeStateStrategy === \"cookie\") {\n\t\t// Retrieve state data from encrypted cookie\n\t\tconst stateCookie = c.context.createAuthCookie(\n\t\t\tsettings?.cookieName ?? \"oauth_state\",\n\t\t);\n\t\tconst encryptedData = c.getCookie(stateCookie.name);\n\n\t\tif (!encryptedData) {\n\t\t\tthrow new StateError(\"State mismatch: auth state cookie not found\", {\n\t\t\t\tcode: \"state_mismatch\",\n\t\t\t\tdetails: { state },\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tconst decryptedData = await symmetricDecrypt({\n\t\t\t\tkey: c.context.secret,\n\t\t\t\tdata: encryptedData,\n\t\t\t});\n\n\t\t\tparsedData = stateDataSchema.parse(JSON.parse(decryptedData));\n\t\t} catch (error) {\n\t\t\tthrow new StateError(\n\t\t\t\t\"State invalid: Failed to decrypt or parse auth state\",\n\t\t\t\t{\n\t\t\t\t\tcode: \"state_invalid\",\n\t\t\t\t\tdetails: { state },\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\t// Clear the cookie after successful parsing\n\t\texpireCookie(c, stateCookie);\n\t} else {\n\t\t// Default: database strategy\n\t\tconst data = await c.context.internalAdapter.findVerificationValue(state);\n\t\tif (!data) {\n\t\t\tthrow new StateError(\"State mismatch: verification not found\", {\n\t\t\t\tcode: \"state_mismatch\",\n\t\t\t\tdetails: { state },\n\t\t\t});\n\t\t}\n\n\t\tparsedData = stateDataSchema.parse(JSON.parse(data.value));\n\n\t\tconst stateCookie = c.context.createAuthCookie(\n\t\t\tsettings?.cookieName ?? \"state\",\n\t\t);\n\n\t\tconst stateCookieValue = await c.getSignedCookie(\n\t\t\tstateCookie.name,\n\t\t\tc.context.secret,\n\t\t);\n\n\t\t/**\n\t\t * This is generally cause security issue and should only be used in\n\t\t * dev or staging environments. It's currently used by the oauth-proxy\n\t\t * plugin\n\t\t */\n\t\tconst skipStateCookieCheck = c.context.oauthConfig.skipStateCookieCheck;\n\t\tif (\n\t\t\t!skipStateCookieCheck &&\n\t\t\t(!stateCookieValue || stateCookieValue !== state)\n\t\t) {\n\t\t\tthrow new StateError(\"State mismatch: State not persisted correctly\", {\n\t\t\t\tcode: \"state_security_mismatch\",\n\t\t\t\tdetails: { state },\n\t\t\t});\n\t\t}\n\n\t\texpireCookie(c, stateCookie);\n\n\t\t// Delete verification value after retrieval\n\t\tawait c.context.internalAdapter.deleteVerificationValue(data.id);\n\t}\n\n\t// Check expiration\n\tif (parsedData.expiresAt < Date.now()) {\n\t\tthrow new StateError(\"Invalid state: request expired\", {\n\t\t\tcode: \"state_mismatch\",\n\t\t\tdetails: {\n\t\t\t\texpiresAt: parsedData.expiresAt,\n\t\t\t},\n\t\t});\n\t}\n\n\treturn parsedData;\n}\n"],"mappings":";;;;;;;AAUA,MAAM,kBAAkB,EAAE,YAAY;CACrC,aAAa,EAAE,QAAQ;CACvB,cAAc,EAAE,QAAQ;CACxB,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,YAAY,EAAE,QAAQ,CAAC,UAAU;CACjC,WAAW,EAAE,QAAQ;CACrB,MAAM,EACJ,OAAO;EACP,OAAO,EAAE,QAAQ;EACjB,QAAQ,EAAE,OAAO,QAAQ;EACzB,CAAC,CACD,UAAU;CACZ,eAAe,EAAE,SAAS,CAAC,UAAU;CACrC,CAAC;AAUF,IAAa,aAAb,cAAgC,gBAAgB;CAC/C;CACA;CAEA,YACC,SACA,SAIC;AACD,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO,QAAQ;AACpB,OAAK,UAAU,QAAQ;;;AAIzB,eAAsB,qBACrB,GACA,WACA,UACC;CACD,MAAM,QAAQ,qBAAqB,GAAG;AAGtC,KAF2B,EAAE,QAAQ,YAAY,uBAEtB,UAAU;EAGpC,MAAM,gBAAgB,MAAM,iBAAiB;GAC5C,KAAK,EAAE,QAAQ;GACf,MAAM,KAAK,UAAU,UAAU;GAC/B,CAAC;EAEF,MAAMA,gBAAc,EAAE,QAAQ,iBAC7B,UAAU,cAAc,eACxB,EACC,QAAQ,KACR,CACD;AAED,IAAE,UAAUA,cAAY,MAAM,eAAeA,cAAY,WAAW;AAEpE,SAAO;GACN;GACA,cAAc,UAAU;GACxB;;CAKF,MAAM,cAAc,EAAE,QAAQ,iBAC7B,UAAU,cAAc,SACxB,EACC,QAAQ,KACR,CACD;AAED,OAAM,EAAE,gBACP,YAAY,MACZ,OACA,EAAE,QAAQ,QACV,YAAY,WACZ;CAED,MAAM,4BAAY,IAAI,MAAM;AAC5B,WAAU,WAAW,UAAU,YAAY,GAAG,GAAG;CAEjD,MAAM,eAAe,MAAM,EAAE,QAAQ,gBAAgB,wBAAwB;EAC5E,OAAO,KAAK,UAAU,UAAU;EAChC,YAAY;EACZ;EACA,CAAC;AAEF,KAAI,CAAC,aACJ,OAAM,IAAI,WACT,uIACA,EACC,MAAM,0BACN,CACD;AAGF,QAAO;EACN,OAAO,aAAa;EACpB,cAAc,UAAU;EACxB;;AAGF,eAAsB,kBACrB,GACA,OACA,UACC;CACD,MAAM,qBAAqB,EAAE,QAAQ,YAAY;CACjD,IAAIC;AAEJ,KAAI,uBAAuB,UAAU;EAEpC,MAAM,cAAc,EAAE,QAAQ,iBAC7B,UAAU,cAAc,cACxB;EACD,MAAM,gBAAgB,EAAE,UAAU,YAAY,KAAK;AAEnD,MAAI,CAAC,cACJ,OAAM,IAAI,WAAW,+CAA+C;GACnE,MAAM;GACN,SAAS,EAAE,OAAO;GAClB,CAAC;AAGH,MAAI;GACH,MAAM,gBAAgB,MAAM,iBAAiB;IAC5C,KAAK,EAAE,QAAQ;IACf,MAAM;IACN,CAAC;AAEF,gBAAa,gBAAgB,MAAM,KAAK,MAAM,cAAc,CAAC;WACrD,OAAO;AACf,SAAM,IAAI,WACT,wDACA;IACC,MAAM;IACN,SAAS,EAAE,OAAO;IAClB,OAAO;IACP,CACD;;AAIF,eAAa,GAAG,YAAY;QACtB;EAEN,MAAM,OAAO,MAAM,EAAE,QAAQ,gBAAgB,sBAAsB,MAAM;AACzE,MAAI,CAAC,KACJ,OAAM,IAAI,WAAW,0CAA0C;GAC9D,MAAM;GACN,SAAS,EAAE,OAAO;GAClB,CAAC;AAGH,eAAa,gBAAgB,MAAM,KAAK,MAAM,KAAK,MAAM,CAAC;EAE1D,MAAM,cAAc,EAAE,QAAQ,iBAC7B,UAAU,cAAc,QACxB;EAED,MAAM,mBAAmB,MAAM,EAAE,gBAChC,YAAY,MACZ,EAAE,QAAQ,OACV;AAQD,MACC,CAF4B,EAAE,QAAQ,YAAY,yBAGjD,CAAC,oBAAoB,qBAAqB,OAE3C,OAAM,IAAI,WAAW,iDAAiD;GACrE,MAAM;GACN,SAAS,EAAE,OAAO;GAClB,CAAC;AAGH,eAAa,GAAG,YAAY;AAG5B,QAAM,EAAE,QAAQ,gBAAgB,wBAAwB,KAAK,GAAG;;AAIjE,KAAI,WAAW,YAAY,KAAK,KAAK,CACpC,OAAM,IAAI,WAAW,kCAAkC;EACtD,MAAM;EACN,SAAS,EACR,WAAW,WAAW,WACtB;EACD,CAAC;AAGH,QAAO"}