Skip to content

Commit

Permalink
add lucia auth option
Browse files Browse the repository at this point in the history
  • Loading branch information
ronanru committed Oct 19, 2023
1 parent d1a8955 commit a2eb038
Show file tree
Hide file tree
Showing 68 changed files with 2,133 additions and 77 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-walls-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-t3-app": minor
---

feat: add lucia auth option
19 changes: 19 additions & 0 deletions cli/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface CliFlags {
/** @internal Used in CI. */
nextAuth: boolean;
/** @internal Used in CI. */
lucia: boolean;
/** @internal Used in CI. */
appRouter: boolean;
}

Expand All @@ -52,6 +54,7 @@ const defaultOptions: CliResults = {
prisma: false,
drizzle: false,
nextAuth: false,
lucia: false,
importAlias: "~/",
appRouter: false,
},
Expand Down Expand Up @@ -100,6 +103,12 @@ export const runCli = async (): Promise<CliResults> => {
"Experimental: Boolean value if we should install NextAuth.js. Must be used in conjunction with `--CI`.",
(value) => !!value && value !== "false"
)
/** @experimental Used for CI E2E tests. Used in conjunction with `--CI` to skip prompting. */
.option(
"--lucia [boolean]",
"Experimental: Boolean value if we should install Lucia Auth. Must be used in conjunction with `--CI`.",
(value) => !!value && value !== "false"
)
/** @experimental - Used for CI E2E tests. Used in conjunction with `--CI` to skip prompting. */
.option(
"--prisma [boolean]",
Expand Down Expand Up @@ -167,6 +176,7 @@ export const runCli = async (): Promise<CliResults> => {
if (cliResults.flags.prisma) cliResults.packages.push("prisma");
if (cliResults.flags.drizzle) cliResults.packages.push("drizzle");
if (cliResults.flags.nextAuth) cliResults.packages.push("nextAuth");
if (cliResults.flags.lucia) cliResults.packages.push("lucia");

if (cliResults.flags.prisma && cliResults.flags.drizzle) {
// We test a matrix of all possible combination of packages in CI. Checking for impossible
Expand All @@ -176,6 +186,13 @@ export const runCli = async (): Promise<CliResults> => {
process.exit(0);
}

if (cliResults.flags.nextAuth && cliResults.flags.lucia) {
logger.warn(
"Incompatible combination NextAuth.js + Lucia Auth. Exiting."
);
process.exit(0);
}

return cliResults;
}

Expand Down Expand Up @@ -237,6 +254,7 @@ export const runCli = async (): Promise<CliResults> => {
options: [
{ value: "none", label: "None" },
{ value: "next-auth", label: "NextAuth.js" },
{ value: "lucia", label: "Lucia Auth" },
// Maybe later
// { value: "clerk", label: "Clerk" },
],
Expand Down Expand Up @@ -301,6 +319,7 @@ export const runCli = async (): Promise<CliResults> => {
if (project.styling) packages.push("tailwind");
if (project.trpc) packages.push("trpc");
if (project.authentication === "next-auth") packages.push("nextAuth");
if (project.authentication === "lucia") packages.push("lucia");
if (project.database === "prisma") packages.push("prisma");
if (project.database === "drizzle") packages.push("drizzle");

Expand Down
38 changes: 26 additions & 12 deletions cli/src/helpers/selectBoilerplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export const selectAppFile = ({

let appFile = "base.tsx";
if (usingNextAuth && usingTRPC) {
appFile = "with-auth-trpc.tsx";
appFile = "with-nextauth-trpc.tsx";
} else if (usingNextAuth && !usingTRPC) {
appFile = "with-auth.tsx";
appFile = "with-nextauth.tsx";
} else if (!usingNextAuth && usingTRPC) {
appFile = "with-trpc.tsx";
}
Expand Down Expand Up @@ -63,13 +63,18 @@ export const selectIndexFile = ({

const usingTRPC = packages.trpc.inUse;
const usingTw = packages.tailwind.inUse;
const usingAuth = packages.nextAuth.inUse;
const usingNextAuth = packages.nextAuth.inUse;
const usingLucia = packages.lucia.inUse;

let indexFile = "base.tsx";
if (usingTRPC && usingTw && usingAuth) {
indexFile = "with-auth-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingAuth) {
indexFile = "with-auth-trpc.tsx";
if (usingTRPC && usingTw && usingNextAuth) {
indexFile = "with-nextauth-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingNextAuth) {
indexFile = "with-nextauth-trpc.tsx";
} else if (usingTRPC && usingTw && usingLucia) {
indexFile = "with-lucia-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingLucia) {
indexFile = "with-lucia-trpc.tsx";
} else if (usingTRPC && usingTw) {
indexFile = "with-trpc-tw.tsx";
} else if (usingTRPC && !usingTw) {
Expand All @@ -92,13 +97,22 @@ export const selectPageFile = ({

const usingTRPC = packages.trpc.inUse;
const usingTw = packages.tailwind.inUse;
const usingAuth = packages.nextAuth.inUse;
const usingLucia = packages.lucia.inUse;
const usingNextAuth = packages.nextAuth.inUse;

let indexFile = "base.tsx";
if (usingTRPC && usingTw && usingAuth) {
indexFile = "with-auth-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingAuth) {
indexFile = "with-auth-trpc.tsx";
if (usingTRPC && usingTw && usingNextAuth) {
indexFile = "with-nextauth-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingNextAuth) {
indexFile = "with-nextauth-trpc.tsx";
} else if (usingTRPC && usingTw && usingLucia) {
indexFile = "with-lucia-trpc-tw.tsx";
} else if (usingTRPC && !usingTw && usingLucia) {
indexFile = "with-lucia-trpc.tsx";
} else if (!usingTRPC && usingTw && usingLucia) {
indexFile = "with-lucia-tw.tsx";
} else if (!usingTRPC && !usingTw && usingLucia) {
indexFile = "with-lucia.tsx";
} else if (usingTRPC && usingTw) {
indexFile = "with-trpc-tw.tsx";
} else if (usingTRPC && !usingTw) {
Expand Down
6 changes: 6 additions & 0 deletions cli/src/installers/dependencyVersionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export const dependencyVersionMap = {
"@next-auth/prisma-adapter": "^1.0.7",
"@auth/drizzle-adapter": "^0.3.2",

// Lucia Auth
lucia: "^2.7.1",
"@lucia-auth/adapter-mysql": "^2.1.0",
"@lucia-auth/adapter-prisma": "^3.0.2",
"@lucia-auth/oauth": "^3.3.1",

// Prisma
prisma: "^5.1.1",
"@prisma/client": "^5.1.1",
Expand Down
16 changes: 9 additions & 7 deletions cli/src/installers/drizzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ export const drizzleInstaller: Installer = ({
const configFile = path.join(extrasDir, "config/drizzle.config.ts");
const configDest = path.join(projectDir, "drizzle.config.ts");

const schemaSrc = path.join(
extrasDir,
"src/server/db",
packages?.nextAuth.inUse
? "drizzle-schema-auth.ts"
: "drizzle-schema-base.ts"
);
let schemaFile = "drizzle-schema-base.ts";
if (packages?.nextAuth.inUse) {
schemaFile = "drizzle-schema-nextauth.ts";
}
if (packages?.lucia.inUse) {
schemaFile = "drizzle-schema-auth.ts";
}

const schemaSrc = path.join(extrasDir, "src/server/db", schemaFile);
const schemaDest = path.join(projectDir, "src/server/db/schema.ts");

// Replace placeholder table prefix with project name
Expand Down
53 changes: 36 additions & 17 deletions cli/src/installers/envVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ import { PKG_ROOT } from "~/consts.js";
import { type Installer } from "~/installers/index.js";

export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {
const usingAuth = packages?.nextAuth.inUse;
const usingNextAuth = packages?.nextAuth.inUse;
const usingLucia = packages?.lucia.inUse;
const usingPrisma = packages?.prisma.inUse;
const usingDrizzle = packages?.drizzle.inUse;

const usingDb = usingPrisma || usingDrizzle;

const envContent = getEnvContent(!!usingAuth, !!usingPrisma, !!usingDrizzle);
const envContent = getEnvContent({
usingDrizzle: !!usingDrizzle,
usingLucia: !!usingLucia,
usingNextAuth: !!usingNextAuth,
usingPrisma: !!usingPrisma,
});

const envFile =
usingAuth && usingDb
? "with-auth-db.mjs"
: usingAuth
? "with-auth.mjs"
: usingDb
? "with-db.mjs"
: "";
let envFile = "";
if (usingDb) envFile = "with-db.mjs";
if (usingNextAuth) envFile = "with-nextauth.mjs";
if (usingNextAuth && usingDb) envFile = "with-nextauth-db.mjs";
if (usingLucia) envFile = "with-lucia.mjs";
if (usingLucia && usingDb) envFile = "with-lucia-db.mjs";

if (envFile !== "") {
const envSchemaSrc = path.join(
Expand All @@ -39,11 +43,17 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {
fs.writeFileSync(envExampleDest, exampleEnvContent + envContent, "utf-8");
};

const getEnvContent = (
usingAuth: boolean,
usingPrisma: boolean,
usingDrizzle: boolean
) => {
const getEnvContent = ({
usingDrizzle,
usingLucia,
usingNextAuth,
usingPrisma,
}: {
usingNextAuth: boolean;
usingLucia: boolean;
usingPrisma: boolean;
usingDrizzle: boolean;
}) => {
let content = `
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
Expand All @@ -67,7 +77,7 @@ DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'
`;
}

if (usingAuth)
if (usingNextAuth)
content += `
# Next Auth
# You can generate a new secret on the command line with:
Expand All @@ -79,9 +89,18 @@ NEXTAUTH_URL="http://localhost:3000"
# Next Auth Discord Provider
DISCORD_CLIENT_ID=""
DISCORD_CLIENT_SECRET=""
`;
if (usingLucia)
content += `
# Lucia Auth
AUTH_URL="http://localhost:3000"
# Lucia Auth Discord Provider
DISCORD_CLIENT_ID=""
DISCORD_CLIENT_SECRET=""
`;

if (!usingAuth && !usingPrisma)
if (!usingNextAuth && !usingPrisma && !usingLucia && !usingDrizzle)
content += `
# Example:
# SERVERVAR="foo"
Expand Down
6 changes: 6 additions & 0 deletions cli/src/installers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { tailwindInstaller } from "~/installers/tailwind.js";
import { trpcInstaller } from "~/installers/trpc.js";
import { type PackageManager } from "~/utils/getUserPkgManager.js";
import { drizzleInstaller } from "./drizzle.js";
import { luciaInstaller } from "./lucia.js";

// Turning this into a const allows the list to be iterated over for programatically creating prompt options
// Should increase extensability in the future
export const availablePackages = [
"nextAuth",
"lucia",
"prisma",
"drizzle",
"tailwind",
Expand Down Expand Up @@ -44,6 +46,10 @@ export const buildPkgInstallerMap = (
inUse: packages.includes("nextAuth"),
installer: nextAuthInstaller,
},
lucia: {
inUse: packages.includes("lucia"),
installer: luciaInstaller,
},
prisma: {
inUse: packages.includes("prisma"),
installer: prismaInstaller,
Expand Down
91 changes: 91 additions & 0 deletions cli/src/installers/lucia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import path from "path";
import fs from "fs-extra";

import { PKG_ROOT } from "~/consts.js";
import { type AvailableDependencies } from "~/installers/dependencyVersionMap.js";
import { type Installer } from "~/installers/index.js";
import { addPackageDependency } from "~/utils/addPackageDependency.js";

export const luciaInstaller: Installer = ({
projectDir,
packages,
appRouter,
}) => {
const usingPrisma = packages?.prisma.inUse;
const usingDrizzle = packages?.drizzle.inUse;
const usingTrpc = packages?.trpc.inUse;

const deps: AvailableDependencies[] = ["lucia", "@lucia-auth/oauth"];
if (usingPrisma) deps.push("@lucia-auth/adapter-prisma");
if (usingDrizzle) deps.push("@lucia-auth/adapter-mysql");

addPackageDependency({
projectDir,
dependencies: deps,
devMode: false,
});

const extrasDir = path.join(PKG_ROOT, "template/extras");

const tsDefinitionFile = "lucia-auth.d.ts";

const signInFile = appRouter
? "src/app/api/auth/discord/signin/route.ts"
: "src/pages/api/auth/discord/signin.ts";
const callbackFile = appRouter
? "src/app/api/auth/discord/callback/route.ts"
: "src/app/api/auth/discord/callback.ts";
const logOutFile = appRouter
? "src/app/api/auth/logout/route.ts"
: "src/pages/api/auth/logout.ts";

const signInSrc = path.join(extrasDir, signInFile);
const signInDest = path.join(projectDir, signInFile);
const callbackSrc = path.join(extrasDir, callbackFile);
const callbackDest = path.join(projectDir, callbackFile);

const tsDefinitionSrc = path.join(extrasDir, tsDefinitionFile);
const tsDefinitionDest = path.join(projectDir, tsDefinitionFile);

const authConfigSrc = path.join(
extrasDir,
"src/server",
appRouter ? "lucia-app" : "lucia-pages",
usingPrisma
? "with-prisma.ts"
: usingDrizzle
? "with-drizzle.ts"
: "base.ts"
);
const authConfigDest = path.join(projectDir, "src/server/auth.ts");

const copySrcDest: [string, string][] = [
[signInSrc, signInDest],
[callbackSrc, callbackDest],
[tsDefinitionSrc, tsDefinitionDest],
[authConfigSrc, authConfigDest],
];

if (appRouter) {
copySrcDest.push([
path.join(
extrasDir,
usingTrpc
? "src/app/_components/logout-button-trpc.tsx"
: "src/app/_components/logout-button.tsx"
),
path.join(projectDir, "src/app/_components/logout-button.tsx"),
]);
}

if (!usingTrpc) {
const logOutSrc = path.join(extrasDir, logOutFile);
const logOutDest = path.join(projectDir, logOutFile);

copySrcDest.push([logOutSrc, logOutDest]);
}

copySrcDest.forEach(([src, dest]) => {
fs.copySync(src, dest);
});
};
2 changes: 1 addition & 1 deletion cli/src/installers/nextAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const nextAuthInstaller: Installer = ({
const authConfigSrc = path.join(
extrasDir,
"src/server",
appRouter ? "auth-app" : "auth-pages",
appRouter ? "nextauth-app" : "nextauth-pages",
usingPrisma
? "with-prisma.ts"
: usingDrizzle
Expand Down
Loading

0 comments on commit a2eb038

Please sign in to comment.