UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

1 lines • 10.4 kB
{"version":3,"file":"state.mjs","names":["stateCookie","parsedData: z.infer<typeof stateDataSchema>"],"sources":["../../src/oauth2/state.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { APIError } from \"better-call\";\nimport * as z from \"zod\";\nimport { setOAuthState } from \"../api/middlewares/oauth\";\nimport {\n\tgenerateRandomString,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../crypto\";\n\nexport async function generateState(\n\tc: GenericEndpointContext,\n\tlink:\n\t\t| {\n\t\t\t\temail: string;\n\t\t\t\tuserId: string;\n\t\t }\n\t\t| undefined,\n\tadditionalData: Record<string, any> | false | undefined,\n) {\n\tconst callbackURL = c.body?.callbackURL || c.context.options.baseURL;\n\tif (!callbackURL) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\tmessage: \"callbackURL is required\",\n\t\t});\n\t}\n\n\tconst codeVerifier = generateRandomString(128);\n\tconst state = generateRandomString(32);\n\tconst storeStateStrategy = c.context.oauthConfig.storeStateStrategy;\n\n\tconst stateData = {\n\t\t...(additionalData ? additionalData : {}),\n\t\tcallbackURL,\n\t\tcodeVerifier,\n\t\terrorURL: c.body?.errorCallbackURL,\n\t\tnewUserURL: c.body?.newUserCallbackURL,\n\t\tlink,\n\t\t/**\n\t\t * This is the actual expiry time of the state\n\t\t */\n\t\texpiresAt: Date.now() + 10 * 60 * 1000,\n\t\trequestSignUp: c.body?.requestSignUp,\n\t\tstate,\n\t};\n\n\tawait setOAuthState(stateData);\n\n\tif (storeStateStrategy === \"cookie\") {\n\t\t// Store state data in an encrypted cookie\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(\"oauth_state\", {\n\t\t\tmaxAge: 10 * 60 * 1000, // 10 minutes\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,\n\t\t};\n\t}\n\n\t// Default: database strategy\n\tconst stateCookie = c.context.createAuthCookie(\"state\", {\n\t\tmaxAge: 5 * 60 * 1000, // 5 minutes\n\t});\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\tconst verification = await c.context.internalAdapter.createVerificationValue({\n\t\tvalue: JSON.stringify(stateData),\n\t\tidentifier: state,\n\t\texpiresAt,\n\t});\n\tif (!verification) {\n\t\tc.context.logger.error(\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);\n\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\tmessage: \"Unable to create verification\",\n\t\t});\n\t}\n\treturn {\n\t\tstate: verification.identifier,\n\t\tcodeVerifier,\n\t};\n}\n\nexport async function parseState(c: GenericEndpointContext) {\n\tconst state = c.query.state || c.body.state;\n\tconst storeStateStrategy = c.context.oauthConfig.storeStateStrategy;\n\n\tconst stateDataSchema = z.looseObject({\n\t\tcallbackURL: z.string(),\n\t\tcodeVerifier: z.string(),\n\t\terrorURL: z.string().optional(),\n\t\tnewUserURL: z.string().optional(),\n\t\texpiresAt: z.number(),\n\t\tlink: z\n\t\t\t.object({\n\t\t\t\temail: z.string(),\n\t\t\t\tuserId: z.coerce.string(),\n\t\t\t})\n\t\t\t.optional(),\n\t\trequestSignUp: z.boolean().optional(),\n\t\tstate: z.string().optional(),\n\t});\n\n\tlet parsedData: z.infer<typeof stateDataSchema>;\n\t/**\n\t * This is generally cause security issue and should only be used in\n\t * dev or staging environments. It's currently used by the oauth-proxy\n\t * plugin\n\t */\n\tconst skipStateCookieCheck = c.context.oauthConfig?.skipStateCookieCheck;\n\tif (storeStateStrategy === \"cookie\") {\n\t\t// Retrieve state data from encrypted cookie\n\t\tconst stateCookie = c.context.createAuthCookie(\"oauth_state\");\n\t\tconst encryptedData = c.getCookie(stateCookie.name);\n\n\t\tif (!encryptedData) {\n\t\t\tc.context.logger.error(\"State Mismatch. OAuth state cookie not found\", {\n\t\t\t\tstate,\n\t\t\t});\n\t\t\tconst errorURL =\n\t\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\t\tthrow c.redirect(`${errorURL}?error=please_restart_the_process`);\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\tc.context.logger.error(\"Failed to decrypt or parse OAuth state cookie\", {\n\t\t\t\terror,\n\t\t\t});\n\t\t\tconst errorURL =\n\t\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\t\tthrow c.redirect(`${errorURL}?error=please_restart_the_process`);\n\t\t}\n\n\t\tconst skipStateCookieCheck = c.context.oauthConfig?.skipStateCookieCheck;\n\t\tif (\n\t\t\t!skipStateCookieCheck &&\n\t\t\tparsedData.state &&\n\t\t\tparsedData.state !== state\n\t\t) {\n\t\t\tc.context.logger.error(\"State Mismatch. State parameter does not match\", {\n\t\t\t\texpected: parsedData.state,\n\t\t\t\treceived: state,\n\t\t\t});\n\t\t\tconst errorURL =\n\t\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\t\tthrow c.redirect(`${errorURL}?error=state_mismatch`);\n\t\t}\n\n\t\t// Clear the cookie after successful parsing\n\t\tc.setCookie(stateCookie.name, \"\", {\n\t\t\tmaxAge: 0,\n\t\t});\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\tc.context.logger.error(\"State Mismatch. Verification not found\", {\n\t\t\t\tstate,\n\t\t\t});\n\t\t\tconst errorURL =\n\t\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\t\tthrow c.redirect(`${errorURL}?error=please_restart_the_process`);\n\t\t}\n\n\t\tparsedData = stateDataSchema.parse(JSON.parse(data.value));\n\n\t\tconst stateCookie = c.context.createAuthCookie(\"state\");\n\t\tconst stateCookieValue = await c.getSignedCookie(\n\t\t\tstateCookie.name,\n\t\t\tc.context.secret,\n\t\t);\n\n\t\tif (\n\t\t\t!skipStateCookieCheck &&\n\t\t\t(!stateCookieValue || stateCookieValue !== state)\n\t\t) {\n\t\t\tconst errorURL =\n\t\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\t\tthrow c.redirect(`${errorURL}?error=state_mismatch`);\n\t\t}\n\t\tc.setCookie(stateCookie.name, \"\", {\n\t\t\tmaxAge: 0,\n\t\t});\n\n\t\t// Delete verification value after retrieval\n\t\tawait c.context.internalAdapter.deleteVerificationValue(data.id);\n\t}\n\n\tif (!parsedData.errorURL) {\n\t\tparsedData.errorURL =\n\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t}\n\n\t// Check expiration\n\tif (parsedData.expiresAt < Date.now()) {\n\t\tconst errorURL =\n\t\t\tc.context.options.onAPIError?.errorURL || `${c.context.baseURL}/error`;\n\t\tthrow c.redirect(`${errorURL}?error=please_restart_the_process`);\n\t}\n\n\tif (parsedData) {\n\t\tawait setOAuthState(parsedData);\n\t}\n\n\treturn parsedData;\n}\n"],"mappings":"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"}