Files
xarticleaudio/src/integrations/better-auth.js

274 lines
6.7 KiB
JavaScript

"use strict";
function sanitizeUserId(userId) {
return String(userId || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
function resolveEmailFromUserId(userId) {
const raw = String(userId || "").trim();
if (raw.includes("@")) {
return raw.toLowerCase();
}
const safe = sanitizeUserId(raw) || "user";
return `${safe}@xartaudio.local`;
}
function extractLegacyUserCookie(cookieHeader) {
const match = String(cookieHeader || "").match(/(?:^|;\s*)xartaudio_user=([^;]+)/);
if (!match) {
return null;
}
try {
return decodeURIComponent(match[1]);
} catch {
return match[1];
}
}
function headersFromObject(rawHeaders = {}) {
const headers = new Headers();
for (const [key, value] of Object.entries(rawHeaders)) {
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
for (const item of value) {
headers.append(key, String(item));
}
continue;
}
headers.set(key, String(value));
}
return headers;
}
function responseHeadersToObject(responseHeaders) {
const headers = {};
for (const [key, value] of responseHeaders.entries()) {
headers[key] = value;
}
if (typeof responseHeaders.getSetCookie === "function") {
const cookies = responseHeaders.getSetCookie();
if (cookies && cookies.length > 0) {
headers["set-cookie"] = cookies.length === 1 ? cookies[0] : cookies;
}
} else {
const cookie = responseHeaders.get("set-cookie");
if (cookie) {
headers["set-cookie"] = cookie;
}
}
return headers;
}
function createBetterAuthAdapter({
appBaseUrl,
basePath = "/api/auth",
secret,
devPassword = "xartaudio-dev-password",
authHandler,
logger = console,
} = {}) {
const normalizedBasePath = basePath.startsWith("/") ? basePath : `/${basePath}`;
const memoryDb = {
user: [],
session: [],
account: [],
verification: [],
};
let handlerPromise = null;
async function resolveHandler() {
if (authHandler) {
return authHandler;
}
if (!handlerPromise) {
handlerPromise = (async () => {
const [{ betterAuth }, { memoryAdapter }] = await Promise.all([
import("better-auth"),
import("better-auth/adapters/memory"),
]);
const auth = betterAuth({
appName: "XArtAudio",
baseURL: appBaseUrl,
basePath: normalizedBasePath,
secret,
trustedOrigins: [appBaseUrl],
database: memoryAdapter(memoryDb),
emailAndPassword: {
enabled: true,
autoSignIn: true,
requireEmailVerification: false,
minPasswordLength: 8,
},
});
return auth.handler;
})().catch((error) => {
logger.error({ err: error }, "failed to initialize better-auth");
handlerPromise = null;
throw error;
});
}
return handlerPromise;
}
async function invoke({ method, path, headers, rawBody = "" }) {
const handler = await resolveHandler();
const requestHeaders = headersFromObject(headers || {});
const url = new URL(path, appBaseUrl).toString();
const body = method === "GET" || method === "HEAD" ? undefined : rawBody;
const response = await handler(new Request(url, {
method,
headers: requestHeaders,
body,
}));
return response;
}
return {
isConfigured() {
return Boolean(appBaseUrl && secret);
},
handlesPath(path) {
return path === normalizedBasePath || path.startsWith(`${normalizedBasePath}/`);
},
async handleRoute({ method, path, headers, rawBody }) {
const response = await invoke({ method, path, headers, rawBody });
const responseBody = await response.text();
return {
status: response.status,
headers: responseHeadersToObject(response.headers),
body: responseBody,
};
},
async getAuthenticatedUserId(headers = {}) {
if (headers["x-user-id"]) {
return String(headers["x-user-id"]);
}
const cookieHeader = headers.cookie || "";
if (!cookieHeader) {
return null;
}
try {
const response = await invoke({
method: "GET",
path: `${normalizedBasePath}/get-session`,
headers: {
cookie: cookieHeader,
accept: "application/json",
},
});
if (!response.ok) {
return null;
}
const payload = await response.json().catch(() => null);
const user = payload && payload.user ? payload.user : null;
if (!user) {
return extractLegacyUserCookie(cookieHeader);
}
return user.name || user.email || user.id || null;
} catch {
return extractLegacyUserCookie(cookieHeader);
}
},
async signInDevUser(userId) {
const email = resolveEmailFromUserId(userId);
const signInBody = JSON.stringify({
email,
password: devPassword,
rememberMe: true,
});
let response = await invoke({
method: "POST",
path: `${normalizedBasePath}/sign-in/email`,
headers: {
"content-type": "application/json",
accept: "application/json",
},
rawBody: signInBody,
});
if (!response.ok) {
response = await invoke({
method: "POST",
path: `${normalizedBasePath}/sign-up/email`,
headers: {
"content-type": "application/json",
accept: "application/json",
},
rawBody: JSON.stringify({
name: String(userId),
email,
password: devPassword,
rememberMe: true,
}),
});
}
if (!response.ok) {
const details = await response.text().catch(() => "");
throw new Error(`auth_sign_in_failed:${response.status}:${details}`);
}
const responseHeaders = responseHeadersToObject(response.headers);
if (!responseHeaders["set-cookie"]) {
throw new Error("auth_set_cookie_missing");
}
return {
setCookie: responseHeaders["set-cookie"],
};
},
async signOut(headers = {}) {
const response = await invoke({
method: "POST",
path: `${normalizedBasePath}/sign-out`,
headers: {
cookie: headers.cookie || "",
accept: "application/json",
},
});
const responseHeaders = responseHeadersToObject(response.headers);
return {
ok: response.ok,
setCookie: responseHeaders["set-cookie"] || null,
};
},
};
}
module.exports = {
createBetterAuthAdapter,
};