UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

162 lines (161 loc) • 6.56 kB
import { getAuthTables } from "@better-auth/core/db"; import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error"; import { filterOutputFields } from "@better-auth/core/utils/db"; //#region src/db/schema.ts const cache = /* @__PURE__ */ new WeakMap(); function getFields(options, modelName, mode) { const cacheKey = `${modelName}:${mode}`; if (!cache.has(options)) cache.set(options, /* @__PURE__ */ new Map()); const tableCache = cache.get(options); if (tableCache.has(cacheKey)) return tableCache.get(cacheKey); const coreSchema = mode === "output" ? getAuthTables(options)[modelName]?.fields ?? {} : {}; const additionalFields = modelName === "user" || modelName === "session" || modelName === "account" ? options[modelName]?.additionalFields : void 0; let schema = { ...coreSchema, ...additionalFields ?? {} }; for (const plugin of options.plugins || []) if (plugin.schema && plugin.schema[modelName]) schema = { ...schema, ...plugin.schema[modelName].fields }; tableCache.set(cacheKey, schema); return schema; } function parseUserOutput(options, user) { return filterOutputFields(user, getFields(options, "user", "output")); } /** * Builds a synthetic user object that matches the shape of a real user * returned from the database. This ensures enumeration protection works * correctly by making synthetic and real user responses indistinguishable. * * The function iterates over the user output schema and: * - Includes all fields that should be returned (returned !== false) * - Uses provided values when available * - Sets optional fields to null when no value is provided * - Applies default values where defined * - Always includes the 'id' field (not part of schema but always present) */ function buildSyntheticUserOutput(options, data) { const schema = getFields(options, "user", "output"); const result = {}; for (const key in schema) { const fieldAttr = schema[key]; if (fieldAttr.returned === false) continue; if (key in data && data[key] !== void 0) result[key] = data[key]; else if (fieldAttr.defaultValue !== void 0) result[key] = typeof fieldAttr.defaultValue === "function" ? fieldAttr.defaultValue() : fieldAttr.defaultValue; else if (!fieldAttr.required) result[key] = null; } if ("id" in data) result.id = data.id; return result; } function parseSessionOutput(options, session) { return filterOutputFields(session, getFields(options, "session", "output")); } function parseAccountOutput(options, account) { const { accessToken: _accessToken, refreshToken: _refreshToken, idToken: _idToken, accessTokenExpiresAt: _accessTokenExpiresAt, refreshTokenExpiresAt: _refreshTokenExpiresAt, password: _password, ...rest } = filterOutputFields(account, getFields(options, "account", "output")); return rest; } function parseInputData(data, schema) { const action = schema.action || "create"; const fields = schema.fields; const parsedData = Object.create(null); for (const key in fields) { if (key in data) { if (fields[key].input === false) { if (fields[key].defaultValue !== void 0) { if (action !== "update") { parsedData[key] = fields[key].defaultValue; continue; } } if (data[key]) throw APIError.from("BAD_REQUEST", { ...BASE_ERROR_CODES.FIELD_NOT_ALLOWED, message: `${key} is not allowed to be set` }); continue; } if (fields[key].validator?.input && data[key] !== void 0) { const result = fields[key].validator.input["~standard"].validate(data[key]); if (result instanceof Promise) throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED); if ("issues" in result && result.issues) throw APIError.from("BAD_REQUEST", { ...BASE_ERROR_CODES.VALIDATION_ERROR, message: result.issues[0]?.message || "Validation Error" }); parsedData[key] = result.value; continue; } if (fields[key].transform?.input && data[key] !== void 0) { parsedData[key] = fields[key].transform?.input(data[key]); continue; } parsedData[key] = data[key]; continue; } if (fields[key].defaultValue !== void 0 && action === "create") { if (typeof fields[key].defaultValue === "function") { parsedData[key] = fields[key].defaultValue(); continue; } parsedData[key] = fields[key].defaultValue; continue; } if (fields[key].required && action === "create") throw APIError.from("BAD_REQUEST", { ...BASE_ERROR_CODES.MISSING_FIELD, message: `${key} is required` }); } return parsedData; } function parseUserInput(options, user = {}, action) { return parseInputData(user, { fields: getFields(options, "user", "input"), action }); } function parseAdditionalUserInputFromProviderProfile(options, profile = {}, action) { const schema = getFields(options, "user", "input"); const allowedProfileFields = Object.create(null); for (const key of Object.keys(profile)) { if (schema[key]?.input === false) continue; allowedProfileFields[key] = profile[key]; } return parseInputData(allowedProfileFields, { fields: schema, action }); } function parseAdditionalUserInput(options, user) { const schema = getFields(options, "user", "input"); return parseInputData(user || {}, { fields: schema }); } function parseAccountInput(options, account) { return parseInputData(account, { fields: getFields(options, "account", "input") }); } function parseSessionInput(options, session, action) { return parseInputData(session, { fields: getFields(options, "session", "input"), action }); } function getSessionDefaultFields(options) { const fields = getFields(options, "session", "input"); const defaults = {}; for (const key in fields) if (fields[key].defaultValue !== void 0) defaults[key] = typeof fields[key].defaultValue === "function" ? fields[key].defaultValue() : fields[key].defaultValue; return defaults; } function mergeSchema(schema, newSchema) { if (!newSchema) return schema; for (const table in newSchema) { const newModelName = newSchema[table]?.modelName; if (newModelName) schema[table].modelName = newModelName; for (const field in schema[table].fields) { const newField = newSchema[table]?.fields?.[field]; if (!newField) continue; schema[table].fields[field].fieldName = newField; } } return schema; } //#endregion export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };