better-auth-cloudflare
Version:
Seamlessly integrate better-auth with Cloudflare Workers, D1, Hyperdrive, KV, R2, and geolocation services.
173 lines (169 loc) • 6 kB
JavaScript
const drizzle = require('better-auth/adapters/drizzle');
const api = require('better-auth/api');
const schema = require('./schema.cjs');
const r2 = require('./r2.cjs');
const client = require('./client.cjs');
const types = require('./types.cjs');
require('zod');
const cloudflare = (options) => {
const opts = options ?? {};
const geolocationTrackingEnabled = opts.geolocationTracking === void 0 || opts.geolocationTracking;
let r2Storage = null;
return {
id: "cloudflare",
schema: schema.schema(opts),
endpoints: {
getGeolocation: api.createAuthEndpoint(
"/cloudflare/geolocation",
{
method: "GET"
},
async (ctx) => {
const session = await api.getSessionFromCtx(ctx);
if (!session) {
return ctx.json({ error: "Unauthorized" }, { status: 401 });
}
const cf = await Promise.resolve(opts.cf);
if (!cf) {
return ctx.json({ error: "Cloudflare context is not available" }, { status: 404 });
}
const context = extractGeolocationData(cf);
return ctx.json(context);
}
),
...opts.r2 ? r2.createR2Endpoints(() => r2Storage, opts.r2) : {}
},
init(init_ctx) {
if (opts.r2) {
r2Storage = r2.createR2Storage(opts.r2, init_ctx.generateId);
}
return {
options: {
databaseHooks: {
session: {
create: {
before: async (s) => {
if (!geolocationTrackingEnabled) {
return s;
}
const cf = await Promise.resolve(opts.cf);
if (!cf) {
return s;
}
const geoData = extractGeolocationData(cf);
s.timezone = geoData.timezone;
s.city = geoData.city;
s.country = geoData.country;
s.region = geoData.region;
s.regionCode = geoData.regionCode;
s.colo = geoData.colo;
s.latitude = geoData.latitude;
s.longitude = geoData.longitude;
return s;
}
}
}
}
}
};
}
};
};
function extractGeolocationData(input) {
if (!input || typeof input !== "object") {
return {};
}
return {
timezone: input.timezone || void 0,
city: input.city || void 0,
country: input.country || void 0,
region: input.region || void 0,
regionCode: input.regionCode || void 0,
colo: input.colo || void 0,
latitude: input.latitude || void 0,
longitude: input.longitude || void 0
};
}
const createKVStorage = (kv) => {
return {
get: async (key) => {
return kv.get(key);
},
set: async (key, value, ttl) => {
return kv.put(key, value, ttl ? { expirationTtl: ttl } : void 0);
},
delete: async (key) => {
return kv.delete(key);
}
};
};
const withCloudflare = (cloudFlareOptions, options) => {
const autoDetectIpEnabled = cloudFlareOptions.autoDetectIpAddress === void 0 || cloudFlareOptions.autoDetectIpAddress === true;
const geolocationTrackingForSession = cloudFlareOptions.geolocationTracking === void 0 || cloudFlareOptions.geolocationTracking === true;
if (autoDetectIpEnabled || geolocationTrackingForSession) {
if (!cloudFlareOptions.cf) {
throw new Error(
"Cloudflare context is required for geolocation or IP detection features. Be sure to pass the `cf` option to the withCloudflare function."
);
}
}
let updatedAdvanced = { ...options.advanced };
if (autoDetectIpEnabled) {
updatedAdvanced.ipAddress = {
...updatedAdvanced.ipAddress ?? {},
ipAddressHeaders: ["cf-connecting-ip", "x-real-ip", ...updatedAdvanced.ipAddress?.ipAddressHeaders ?? []]
};
} else if (updatedAdvanced.ipAddress?.ipAddressHeaders) ;
let updatedSession = { ...options.session };
if (geolocationTrackingForSession) {
updatedSession.storeSessionInDatabase = true;
} else if (options.session?.storeSessionInDatabase === void 0) ;
const dbConfigs = [cloudFlareOptions.postgres, cloudFlareOptions.mysql, cloudFlareOptions.d1].filter(Boolean);
if (dbConfigs.length > 1) {
throw new Error(
"Only one database configuration can be provided. Please provide only one of postgres, mysql, or d1."
);
}
let database;
if (cloudFlareOptions.postgres) {
database = drizzle.drizzleAdapter(cloudFlareOptions.postgres.db, {
provider: "pg",
...cloudFlareOptions.postgres.options
});
} else if (cloudFlareOptions.mysql) {
database = drizzle.drizzleAdapter(cloudFlareOptions.mysql.db, {
provider: "mysql",
...cloudFlareOptions.mysql.options
});
} else if (cloudFlareOptions.d1) {
database = drizzle.drizzleAdapter(cloudFlareOptions.d1.db, {
provider: "sqlite",
...cloudFlareOptions.d1.options
});
}
return {
...options,
database,
secondaryStorage: cloudFlareOptions.kv ? createKVStorage(cloudFlareOptions.kv) : void 0,
plugins: [cloudflare(cloudFlareOptions), ...options.plugins ?? []],
advanced: updatedAdvanced,
session: updatedSession
};
};
exports.schema = schema.schema;
exports.R2_ERROR_CODES = r2.R2_ERROR_CODES;
exports.createFileMetadataSchema = r2.createFileMetadataSchema;
exports.createFileValidator = r2.createFileValidator;
exports.createR2Endpoints = r2.createR2Endpoints;
exports.createR2Storage = r2.createR2Storage;
exports.createUploadFileSchema = r2.createUploadFileSchema;
exports.error = r2.error;
exports.fileIdSchema = r2.fileIdSchema;
exports.listFilesSchema = r2.listFilesSchema;
exports.success = r2.success;
exports.cloudflareClient = client.cloudflareClient;
exports.createR2Config = types.createR2Config;
exports.cloudflare = cloudflare;
exports.createKVStorage = createKVStorage;
exports.withCloudflare = withCloudflare;
;