better-auth
Version:
The most comprehensive authentication framework for TypeScript.
162 lines (161 loc) • 6.56 kB
JavaScript
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 };