@npio/internals
Version:
A free visual website editor, powered with your own SolidJS components.
153 lines (128 loc) • 3.97 kB
text/typescript
import { createId } from "@paralleldrive/cuid2";
import { decode } from "decode-formdata";
import { getRequestEvent } from "solid-js/web";
import {
H3Event,
clearSession,
getCookie,
getSession,
updateSession,
useSession,
type SessionConfig,
} from "vinxi/http";
import { Auth, HandlerInput, csrfHeader } from ".";
import { defineRequestFunction } from "../src/cache";
export type Config = {
password: string;
cookie?: SessionConfig["cookie"];
};
export type ServerOptions<T = any, P = any> = {
config: () => Config;
signin: (args: P) => T;
};
const sessionExists = function <T>(config: SessionConfig & { name: string }) {
const event = getRequestEvent()!;
const cachedSession = !!((event as any).nativeEvent as H3Event).context
.sessions?.[config.name];
const sessionCookie = !!getCookie(config.name);
return cachedSession || sessionCookie;
};
const refreshSession = async function <T>(
config: SessionConfig & { name: string },
) {
const event = getRequestEvent() as any;
const nativeEvent = (event as any).nativeEvent as H3Event;
const refreshedSessions = (event.locals.npRefreshedSessions =
event.locals.npRefreshedSessions || {});
if (nativeEvent.handled || refreshedSessions[config.name]) {
return;
}
refreshedSessions[config.name] = true;
await updateSession(config);
};
const getSessionLazy = async function <T>(
config: SessionConfig & { name: string },
) {
const event = getRequestEvent()!;
// getSession would try to create a session if it doesnt exist
if (!sessionExists(config)) {
return;
}
if (!event.router?.dataOnly) {
await refreshSession(config);
}
return await getSession<{ csrf: string; data: T }>(config);
};
export const createServer = function <T, P>(
name: string,
options: ServerOptions<T, P>,
) {
const getSessionConfig = defineRequestFunction("auth-" + name, () => {
const config = options.config();
const sameSite = (config.cookie && config.cookie.sameSite) || "lax";
return {
name: name,
password: config.password,
cookie: {
path: "/",
sameSite,
secure: sameSite === "none",
...config.cookie,
},
} satisfies SessionConfig;
});
const assertCsrf = async function (csrf: string | true) {
const sessionConfig = getSessionConfig();
const session = await getSessionLazy(sessionConfig);
if (!session) {
throw new Error("Access denied!");
}
if (csrf === true) {
const csrfHeader_ = csrfHeader(sessionConfig.name);
csrf = getRequestEvent()!.request.headers.get(csrfHeader_) || "";
}
if (session.data.csrf !== csrf) {
throw new Error("Access denied!");
}
};
const getSession_ = async function () {
const session = await getSessionLazy<Awaited<T>>(getSessionConfig());
if (!session) return;
return {
...session.data,
createdAt: session.createdAt,
};
};
const signin = async function (args: P) {
const data = await options.signin(args);
if (!data) return false;
const session = await useSession(getSessionConfig());
await session.update({ data, csrf: createId() });
};
const handler = async function (input: HandlerInput<P>) {
const genericInput = input instanceof FormData ? decode(input) : input;
const { type, ...rest } = genericInput;
if (type === "session") {
return await getSession_();
} else if (type === "signin") {
return await signin(rest);
} else if (type === "signout") {
await clearSession(getSessionConfig());
return;
} else if (type === "refresh") {
const sessionConfig = getSessionConfig();
if (!sessionExists(sessionConfig)) {
return;
}
await refreshSession(sessionConfig);
}
};
const Auth: Auth<Exclude<Awaited<T>, undefined>, P> = undefined!;
return {
handler,
getSession: getSession_,
assertCsrf,
Auth,
signin,
};
};