@zpg6-test-pkgs/better-auth
Version:
The most comprehensive authentication library for TypeScript.
790 lines (785 loc) • 26.3 kB
JavaScript
;
const json = require('./better-auth.vPQBmXQL.cjs');
const getTables = require('./better-auth.BEphVDyL.cjs');
const id = require('./better-auth.Bg6iw3ig.cjs');
require('zod/v4');
require('better-call');
require('@better-auth/utils/hash');
require('@noble/ciphers/chacha');
require('@noble/ciphers/utils');
require('@noble/ciphers/webcrypto');
require('@better-auth/utils/base64');
require('jose');
require('@noble/hashes/scrypt');
require('@better-auth/utils');
require('@better-auth/utils/hex');
require('@noble/hashes/utils');
require('./better-auth.CYeOI8C-.cjs');
const logger = require('./better-auth.B3274wGK.cjs');
function withApplyDefault(value, field, action) {
if (action === "update") {
return value;
}
if (value === void 0 || value === null) {
if (field.defaultValue !== void 0) {
if (typeof field.defaultValue === "function") {
return field.defaultValue();
}
return field.defaultValue;
}
}
return value;
}
let debugLogs = [];
let transactionId = -1;
const colors = {
reset: "\x1B[0m",
bright: "\x1B[1m",
dim: "\x1B[2m",
fg: {
yellow: "\x1B[33m",
magenta: "\x1B[35m"},
bg: {
black: "\x1B[40m"}
};
const createAdapter = ({
adapter,
config: cfg
}) => (options) => {
const config = {
...cfg,
supportsBooleans: cfg.supportsBooleans ?? true,
supportsDates: cfg.supportsDates ?? true,
supportsJSON: cfg.supportsJSON ?? false,
adapterName: cfg.adapterName ?? cfg.adapterId,
supportsNumericIds: cfg.supportsNumericIds ?? true
};
if (options.advanced?.database?.useNumberId === true && config.supportsNumericIds === false) {
throw new Error(
`[${config.adapterName}] Your database or database adapter does not support numeric ids. Please disable "useNumberId" in your config.`
);
}
const schema = getTables.getAuthTables(options);
const getDefaultFieldName = ({
field,
model: unsafe_model
}) => {
if (field === "id" || field === "_id") {
return "id";
}
const model = getDefaultModelName(unsafe_model);
let f = schema[model]?.fields[field];
if (!f) {
f = Object.values(schema[model]?.fields).find(
(f2) => f2.fieldName === field
);
}
if (!f) {
debugLog(`Field ${field} not found in model ${model}`);
debugLog(`Schema:`, schema);
throw new Error(`Field ${field} not found in model ${model}`);
}
return field;
};
const getDefaultModelName = (model) => {
if (config.usePlural && model.charAt(model.length - 1) === "s") {
let pluralessModel = model.slice(0, -1);
let m2 = schema[pluralessModel] ? pluralessModel : void 0;
if (!m2) {
m2 = Object.entries(schema).find(
([_, f]) => f.modelName === pluralessModel
)?.[0];
}
if (m2) {
return m2;
}
}
let m = schema[model] ? model : void 0;
if (!m) {
m = Object.entries(schema).find(([_, f]) => f.modelName === model)?.[0];
}
if (!m) {
debugLog(`Model "${model}" not found in schema`);
debugLog(`Schema:`, schema);
throw new Error(`Model "${model}" not found in schema`);
}
return m;
};
const getModelName = (model) => {
const defaultModelKey = getDefaultModelName(model);
const usePlural = config && config.usePlural;
const useCustomModelName = schema && schema[defaultModelKey] && schema[defaultModelKey].modelName !== model;
if (useCustomModelName) {
return usePlural ? `${schema[defaultModelKey].modelName}s` : schema[defaultModelKey].modelName;
}
return usePlural ? `${model}s` : model;
};
function getFieldName({
model: model_name,
field: field_name
}) {
const model = getDefaultModelName(model_name);
const field = getDefaultFieldName({ model, field: field_name });
return schema[model]?.fields[field]?.fieldName || field;
}
const debugLog = (...args) => {
if (config.debugLogs === true || typeof config.debugLogs === "object") {
if (typeof config.debugLogs === "object" && "isRunningAdapterTests" in config.debugLogs) {
if (config.debugLogs.isRunningAdapterTests) {
args.shift();
debugLogs.push(args);
}
return;
}
if (typeof config.debugLogs === "object" && config.debugLogs.logCondition && !config.debugLogs.logCondition?.()) {
return;
}
if (typeof args[0] === "object" && "method" in args[0]) {
const method = args.shift().method;
if (typeof config.debugLogs === "object") {
if (method === "create" && !config.debugLogs.create) {
return;
} else if (method === "update" && !config.debugLogs.update) {
return;
} else if (method === "updateMany" && !config.debugLogs.updateMany) {
return;
} else if (method === "findOne" && !config.debugLogs.findOne) {
return;
} else if (method === "findMany" && !config.debugLogs.findMany) {
return;
} else if (method === "delete" && !config.debugLogs.delete) {
return;
} else if (method === "deleteMany" && !config.debugLogs.deleteMany) {
return;
} else if (method === "count" && !config.debugLogs.count) {
return;
}
}
logger.logger.info(`[${config.adapterName}]`, ...args);
} else {
logger.logger.info(`[${config.adapterName}]`, ...args);
}
}
};
const idField = ({
customModelName,
forceAllowId
}) => {
const shouldGenerateId = !config.disableIdGeneration && !options.advanced?.database?.useNumberId && !forceAllowId;
const model = getDefaultModelName(customModelName ?? "id");
return {
type: options.advanced?.database?.useNumberId ? "number" : "string",
required: shouldGenerateId ? true : false,
...shouldGenerateId ? {
defaultValue() {
if (config.disableIdGeneration) return void 0;
const useNumberId = options.advanced?.database?.useNumberId;
let generateId = options.advanced?.database?.generateId;
if (options.advanced?.generateId !== void 0) {
logger.logger.warn(
"Your Better Auth config includes advanced.generateId which is deprecated. Please use advanced.database.generateId instead. This will be removed in future releases."
);
generateId = options.advanced?.generateId;
}
if (generateId === false || useNumberId) return void 0;
if (generateId) {
return generateId({
model
});
}
if (config.customIdGenerator) {
return config.customIdGenerator({ model });
}
return id.generateId();
}
} : {}
};
};
const getFieldAttributes = ({
model,
field
}) => {
const defaultModelName = getDefaultModelName(model);
const defaultFieldName = getDefaultFieldName({
field,
model
});
const fields = schema[defaultModelName].fields;
fields.id = idField({ customModelName: defaultModelName });
return fields[defaultFieldName];
};
const adapterInstance = adapter({
options,
schema,
debugLog,
getFieldName,
getModelName,
getDefaultModelName,
getDefaultFieldName,
getFieldAttributes
});
const transformInput = async (data, unsafe_model, action, forceAllowId) => {
const transformedData = {};
const fields = schema[unsafe_model].fields;
const newMappedKeys = config.mapKeysTransformInput ?? {};
if (!config.disableIdGeneration && !options.advanced?.database?.useNumberId) {
fields.id = idField({
customModelName: unsafe_model,
forceAllowId: forceAllowId && "id" in data
});
}
for (const field in fields) {
const value = data[field];
const fieldAttributes = fields[field];
let newFieldName = newMappedKeys[field] || fields[field].fieldName || field;
if (value === void 0 && (!fieldAttributes.defaultValue && !fieldAttributes.transform?.input || action === "update")) {
continue;
}
let newValue = withApplyDefault(value, fieldAttributes, action);
if (fieldAttributes.transform?.input) {
newValue = await fieldAttributes.transform.input(newValue);
}
if (fieldAttributes.references?.field === "id" && options.advanced?.database?.useNumberId) {
if (Array.isArray(newValue)) {
newValue = newValue.map(Number);
} else {
newValue = Number(newValue);
}
} else if (config.supportsJSON === false && typeof newValue === "object" && //@ts-expect-error -Future proofing
fieldAttributes.type === "json") {
newValue = JSON.stringify(newValue);
} else if (config.supportsDates === false && newValue instanceof Date && fieldAttributes.type === "date") {
newValue = newValue.toISOString();
} else if (config.supportsBooleans === false && typeof newValue === "boolean") {
newValue = newValue ? 1 : 0;
}
if (config.customTransformInput) {
newValue = config.customTransformInput({
data: newValue,
action,
field: newFieldName,
fieldAttributes,
model: unsafe_model,
schema,
options
});
}
transformedData[newFieldName] = newValue;
}
return transformedData;
};
const transformOutput = async (data, unsafe_model, select = []) => {
if (!data) return null;
const newMappedKeys = config.mapKeysTransformOutput ?? {};
const transformedData = {};
const tableSchema = schema[unsafe_model].fields;
const idKey = Object.entries(newMappedKeys).find(
([_, v]) => v === "id"
)?.[0];
tableSchema[idKey ?? "id"] = {
type: options.advanced?.database?.useNumberId ? "number" : "string"
};
for (const key in tableSchema) {
if (select.length && !select.includes(key)) {
continue;
}
const field = tableSchema[key];
if (field) {
const originalKey = field.fieldName || key;
let newValue = data[Object.entries(newMappedKeys).find(
([_, v]) => v === originalKey
)?.[0] || originalKey];
if (field.transform?.output) {
newValue = await field.transform.output(newValue);
}
let newFieldName = newMappedKeys[key] || key;
if (originalKey === "id" || field.references?.field === "id") {
if (typeof newValue !== "undefined") newValue = String(newValue);
} else if (config.supportsJSON === false && typeof newValue === "string" && //@ts-expect-error - Future proofing
field.type === "json") {
newValue = json.safeJSONParse(newValue);
} else if (config.supportsDates === false && typeof newValue === "string" && field.type === "date") {
newValue = new Date(newValue);
} else if (config.supportsBooleans === false && typeof newValue === "number" && field.type === "boolean") {
newValue = newValue === 1;
}
if (config.customTransformOutput) {
newValue = config.customTransformOutput({
data: newValue,
field: newFieldName,
fieldAttributes: field,
select,
model: unsafe_model,
schema,
options
});
}
transformedData[newFieldName] = newValue;
}
}
return transformedData;
};
const transformWhereClause = ({
model,
where
}) => {
if (!where) return void 0;
const newMappedKeys = config.mapKeysTransformInput ?? {};
return where.map((w) => {
const {
field: unsafe_field,
value,
operator = "eq",
connector = "AND"
} = w;
if (operator === "in") {
if (!Array.isArray(value)) {
throw new Error("Value must be an array");
}
}
const defaultModelName = getDefaultModelName(model);
const defaultFieldName = getDefaultFieldName({
field: unsafe_field,
model
});
const fieldName = newMappedKeys[defaultFieldName] || getFieldName({
field: defaultFieldName,
model: defaultModelName
});
const fieldAttr = getFieldAttributes({
field: defaultFieldName,
model: defaultModelName
});
if (defaultFieldName === "id" || fieldAttr.references?.field === "id") {
if (options.advanced?.database?.useNumberId) {
if (Array.isArray(value)) {
return {
operator,
connector,
field: fieldName,
value: value.map(Number)
};
}
return {
operator,
connector,
field: fieldName,
value: Number(value)
};
}
}
return {
operator,
connector,
field: fieldName,
value
};
});
};
return {
create: async ({
data: unsafeData,
model: unsafeModel,
select,
forceAllowId = false
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
if ("id" in unsafeData && !forceAllowId) {
logger.logger.warn(
`[${config.adapterName}] - You are trying to create a record with an id. This is not allowed as we handle id generation for you, unless you pass in the \`forceAllowId\` parameter. The id will be ignored.`
);
const err = new Error();
const stack = err.stack?.split("\n").filter((_, i) => i !== 1).join("\n").replace("Error:", "Create method with `id` being called at:");
console.log(stack);
unsafeData.id = void 0;
}
debugLog(
{ method: "create" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 4)}`,
`${formatMethod("create")} ${formatAction("Unsafe Input")}:`,
{ model, data: unsafeData }
);
const data = await transformInput(
unsafeData,
unsafeModel,
"create",
forceAllowId
);
debugLog(
{ method: "create" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 4)}`,
`${formatMethod("create")} ${formatAction("Parsed Input")}:`,
{ model, data }
);
const res = await adapterInstance.create({ data, model });
debugLog(
{ method: "create" },
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 4)}`,
`${formatMethod("create")} ${formatAction("DB Result")}:`,
{ model, res }
);
const transformed = await transformOutput(res, unsafeModel, select);
debugLog(
{ method: "create" },
`${formatTransactionId(thisTransactionId)} ${formatStep(4, 4)}`,
`${formatMethod("create")} ${formatAction("Parsed Result")}:`,
{ model, data: transformed }
);
return transformed;
},
update: async ({
model: unsafeModel,
where: unsafeWhere,
update: unsafeData
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "update" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 4)}`,
`${formatMethod("update")} ${formatAction("Unsafe Input")}:`,
{ model, data: unsafeData }
);
const data = await transformInput(
unsafeData,
unsafeModel,
"update"
);
debugLog(
{ method: "update" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 4)}`,
`${formatMethod("update")} ${formatAction("Parsed Input")}:`,
{ model, data }
);
const res = await adapterInstance.update({
model,
where,
update: data
});
debugLog(
{ method: "update" },
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 4)}`,
`${formatMethod("update")} ${formatAction("DB Result")}:`,
{ model, data: res }
);
const transformed = await transformOutput(res, unsafeModel);
debugLog(
{ method: "update" },
`${formatTransactionId(thisTransactionId)} ${formatStep(4, 4)}`,
`${formatMethod("update")} ${formatAction("Parsed Result")}:`,
{ model, data: transformed }
);
return transformed;
},
updateMany: async ({
model: unsafeModel,
where: unsafeWhere,
update: unsafeData
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "updateMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 4)}`,
`${formatMethod("updateMany")} ${formatAction("Unsafe Input")}:`,
{ model, data: unsafeData }
);
const data = await transformInput(unsafeData, unsafeModel, "update");
debugLog(
{ method: "updateMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 4)}`,
`${formatMethod("updateMany")} ${formatAction("Parsed Input")}:`,
{ model, data }
);
const updatedCount = await adapterInstance.updateMany({
model,
where,
update: data
});
debugLog(
{ method: "updateMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 4)}`,
`${formatMethod("updateMany")} ${formatAction("DB Result")}:`,
{ model, data: updatedCount }
);
debugLog(
{ method: "updateMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(4, 4)}`,
`${formatMethod("updateMany")} ${formatAction("Parsed Result")}:`,
{ model, data: updatedCount }
);
return updatedCount;
},
findOne: async ({
model: unsafeModel,
where: unsafeWhere,
select
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "findOne" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 3)}`,
`${formatMethod("findOne")}:`,
{ model, where, select }
);
const res = await adapterInstance.findOne({
model,
where,
select
});
debugLog(
{ method: "findOne" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 3)}`,
`${formatMethod("findOne")} ${formatAction("DB Result")}:`,
{ model, data: res }
);
const transformed = await transformOutput(
res,
unsafeModel,
select
);
debugLog(
{ method: "findOne" },
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 3)}`,
`${formatMethod("findOne")} ${formatAction("Parsed Result")}:`,
{ model, data: transformed }
);
return transformed;
},
findMany: async ({
model: unsafeModel,
where: unsafeWhere,
limit: unsafeLimit,
sortBy,
offset
}) => {
transactionId++;
let thisTransactionId = transactionId;
const limit = unsafeLimit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "findMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 3)}`,
`${formatMethod("findMany")}:`,
{ model, where, limit, sortBy, offset }
);
const res = await adapterInstance.findMany({
model,
where,
limit,
sortBy,
offset
});
debugLog(
{ method: "findMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 3)}`,
`${formatMethod("findMany")} ${formatAction("DB Result")}:`,
{ model, data: res }
);
const transformed = await Promise.all(
res.map(async (r) => await transformOutput(r, unsafeModel))
);
debugLog(
{ method: "findMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 3)}`,
`${formatMethod("findMany")} ${formatAction("Parsed Result")}:`,
{ model, data: transformed }
);
return transformed;
},
delete: async ({
model: unsafeModel,
where: unsafeWhere
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "delete" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 2)}`,
`${formatMethod("delete")}:`,
{ model, where }
);
await adapterInstance.delete({
model,
where
});
debugLog(
{ method: "delete" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 2)}`,
`${formatMethod("delete")} ${formatAction("DB Result")}:`,
{ model }
);
},
deleteMany: async ({
model: unsafeModel,
where: unsafeWhere
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "deleteMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 2)}`,
`${formatMethod("deleteMany")} ${formatAction("DeleteMany")}:`,
{ model, where }
);
const res = await adapterInstance.deleteMany({
model,
where
});
debugLog(
{ method: "deleteMany" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 2)}`,
`${formatMethod("deleteMany")} ${formatAction("DB Result")}:`,
{ model, data: res }
);
return res;
},
count: async ({
model: unsafeModel,
where: unsafeWhere
}) => {
transactionId++;
let thisTransactionId = transactionId;
const model = getModelName(unsafeModel);
const where = transformWhereClause({
model: unsafeModel,
where: unsafeWhere
});
debugLog(
{ method: "count" },
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 2)}`,
`${formatMethod("count")}:`,
{
model,
where
}
);
const res = await adapterInstance.count({
model,
where
});
debugLog(
{ method: "count" },
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 2)}`,
`${formatMethod("count")}:`,
{
model,
data: res
}
);
return res;
},
createSchema: adapterInstance.createSchema ? async (_, file) => {
const tables = getTables.getAuthTables(options);
if (options.secondaryStorage && !options.session?.storeSessionInDatabase) {
delete tables.session;
}
if (options.rateLimit && options.rateLimit.storage === "database" && // rate-limit will default to enabled in production,
// and given storage is database, it will try to use the rate-limit table,
// so we should make sure to generate rate-limit table schema
(typeof options.rateLimit.enabled === "undefined" || // and of course if they forcefully set to true, then they want rate-limit,
// thus we should also generate rate-limit table schema
options.rateLimit.enabled === true)) {
tables.ratelimit = {
modelName: options.rateLimit.modelName ?? "ratelimit",
fields: {
key: {
type: "string",
unique: true,
required: true,
fieldName: options.rateLimit.fields?.key ?? "key"
},
count: {
type: "number",
required: true,
fieldName: options.rateLimit.fields?.count ?? "count"
},
lastRequest: {
type: "number",
required: true,
bigint: true,
defaultValue: () => Date.now(),
fieldName: options.rateLimit.fields?.lastRequest ?? "lastRequest"
}
}
};
}
return adapterInstance.createSchema({ file, tables });
} : void 0,
options: {
adapterConfig: config,
...adapterInstance.options ?? {}
},
id: config.adapterId,
// Secretly export values ONLY if this adapter has enabled adapter-test-debug-logs.
// This would then be used during our adapter-tests to help print debug logs if a test fails.
//@ts-expect-error - ^^
...config.debugLogs?.isRunningAdapterTests ? {
adapterTestDebugLogs: {
resetDebugLogs() {
debugLogs = [];
},
printDebugLogs() {
const separator = `\u2500`.repeat(80);
let log = debugLogs.reverse().map((log2) => {
log2[0] = `
${log2[0]}`;
return [...log2, "\n"];
}).reduce(
(prev, curr) => {
return [...curr, ...prev];
},
[`
${separator}`]
);
console.log(...log);
}
}
} : {}
};
};
function formatTransactionId(transactionId2) {
return `${colors.fg.magenta}#${transactionId2}${colors.reset}`;
}
function formatStep(step, total) {
return `${colors.bg.black}${colors.fg.yellow}[${step}/${total}]${colors.reset}`;
}
function formatMethod(method) {
return `${colors.bright}${method}${colors.reset}`;
}
function formatAction(action) {
return `${colors.dim}(${action})${colors.reset}`;
}
exports.createAdapter = createAdapter;