import { Auth, type AuthConfig, createActionURL, setEnvDefaults, } from "@auth/core"; // import CredentialsProvider from "@auth/core/providers/credentials"; import GoogleProvider from "@auth/core/providers/google"; import type { Session } from "@auth/core/types"; // TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.) import type { Get, UniversalHandler, UniversalMiddleware, } from "@universal-middleware/core"; import { env } from "./env.js"; import { getDbClient } from "../database/index.js"; import { JWT } from "@auth/core/jwt"; const POSTGRES_CONNECTION_STRING = "postgres://neondb_owner:npg_sOVmj8vWq2zG@ep-withered-king-adiz9gpi-pooler.c-2.us-east-1.aws.neon.tech:5432/neondb?sslmode=require&channel_binding=true"; if (!globalThis.crypto) { /** * Polyfill needed if Auth.js code runs on node18 */ Object.defineProperty(globalThis, "crypto", { value: await import("node:crypto").then( (crypto) => crypto.webcrypto as Crypto ), writable: false, configurable: true, }); } const authjsConfig = { basePath: "/api/auth", // trustHost: Boolean( // env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production" // ), trustHost: true, // TODO: Replace secret {@see https://authjs.dev/reference/core#secret} secret: "buginoo", /** Needed to specify cookie name because for some reason in production it * wasn't reading the correct cookie but in development it was. So we need to * make sure both development and production are using the same cookie name.*/ cookies: { state: { name: "__Secure-authjs.session-token" }, }, providers: [ // TODO: Choose and implement providers // CredentialsProvider({ // name: "Credentials", // credentials: { // username: { label: "Username", type: "text", placeholder: "jsmith" }, // password: { label: "Password", type: "password" }, // }, // async authorize() { // // Add logic here to look up the user from the credentials supplied // const user = { // id: "019900bb-61b3-7333-b760-b27784dfe33b", // name: "J Smith", // email: "jsmith@example.com", // }; // // Any object returned will be saved in `user` property of the JWT // // If you return null then an error will be displayed advising the user to check their details. // // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter // return user ?? null; // }, // }), GoogleProvider({ clientId: "697711350664-t6237s5n3ttjd1npp1qif1aupptkr0va.apps.googleusercontent.com", clientSecret: "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", }), ], callbacks: { async signIn({ user, account, profile }) { if (typeof user?.email !== "string") return false; const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING); let userFromDb = await dbClient .selectFrom("users") .selectAll() .where("email", "=", user.email) .executeTakeFirst(); if (!userFromDb) { userFromDb = ( await dbClient .insertInto("users") .values({ email: user.email, username: user.email, password: null, createdAt: null, lastLogin: null, }) .returningAll() .execute() )[0]; } console.log("signIn", user, account, profile); return true; }, jwt: async ({ token }) => { if (typeof token?.email !== "string") return token; const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING); let userFromDb = await dbClient .selectFrom("users") .selectAll() .where("email", "=", token.email || "") .executeTakeFirst(); /** TODO: the following should never happen, because the account in * created in the `isgnIn` step; but I don't know what error to throw here * if for some reason there is no such account.*/ if (!userFromDb) { userFromDb = ( await dbClient .insertInto("users") .values({ email: token.email, username: token.email, password: null, createdAt: null, lastLogin: null, }) .returningAll() .execute() )[0]; } return { ...token, id: userFromDb?.id || "", }; }, session: ({ token, session }) => { return { ...session, user: { ...session.user, id: token.id as string, }, jwt: token, }; }, }, } satisfies Omit; /** * Retrieve Auth.js session from Request */ export async function getSession( req: Request, config: Omit ): Promise { setEnvDefaults(process.env, config); const requestURL = new URL(req.url); const url = createActionURL( "session", requestURL.protocol, req.headers, process.env, config ); const response = await Auth( new Request(url, { headers: { cookie: req.headers.get("cookie") ?? "" } }), config ); const { status = 200 } = response; const data = await response.json(); if (!data || !Object.keys(data).length) return null; if (status === 200) return data; throw new Error(data.message); } /** * Add Auth.js session to context * @link {@see https://authjs.dev/getting-started/session-management/get-session} **/ export const authjsSessionMiddleware: Get<[], UniversalMiddleware> = () => async (request, context) => { try { return { ...context, session: await getSession(request, authjsConfig), }; } catch (error) { console.debug("authjsSessionMiddleware:", error); return { ...context, session: null, }; } }; /** * Auth.js route * @link {@see https://authjs.dev/getting-started/installation} **/ export const authjsHandler = (() => async (request) => { return Auth(request, authjsConfig); }) satisfies Get<[], UniversalHandler>;