You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
6.0 KiB
TypeScript

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"
),
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
secret: "buginoo",
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<AuthConfig, "raw">;
/**
* Retrieve Auth.js session from Request
*/
export async function getSession(
req: Request,
config: Omit<AuthConfig, "raw">
): Promise<Session | null> {
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>;