mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
simplify auth setup
This commit is contained in:
5
.changeset/busy-foxes-love.md
Normal file
5
.changeset/busy-foxes-love.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
simplify auth setup
|
||||||
@@ -5,7 +5,7 @@ import pc from "picocolors";
|
|||||||
import { PKG_ROOT } from "../constants";
|
import { PKG_ROOT } from "../constants";
|
||||||
import type { ProjectConfig } from "../types";
|
import type { ProjectConfig } from "../types";
|
||||||
|
|
||||||
export async function configureAuth(
|
export async function setupAuth(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
enableAuth: boolean,
|
enableAuth: boolean,
|
||||||
hasDatabase: boolean,
|
hasDatabase: boolean,
|
||||||
@@ -16,177 +16,98 @@ export async function configureAuth(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!enableAuth) {
|
if (!enableAuth) {
|
||||||
await fs.remove(path.join(clientDir, "src/components/sign-up-form.tsx"));
|
return;
|
||||||
await fs.remove(path.join(clientDir, "src/components/sign-in-form.tsx"));
|
}
|
||||||
await fs.remove(path.join(clientDir, "src/components/auth-forms.tsx"));
|
|
||||||
await fs.remove(path.join(clientDir, "src/components/user-menu.tsx"));
|
|
||||||
await fs.remove(path.join(clientDir, "src/lib/auth-client.ts"));
|
|
||||||
|
|
||||||
const indexRoutePath = path.join(clientDir, "src/routes/index.tsx");
|
if (!hasDatabase) {
|
||||||
const indexRouteContent = await fs.readFile(indexRoutePath, "utf8");
|
|
||||||
const updatedIndexRouteContent = indexRouteContent
|
|
||||||
.replace(/import AuthForms from "@\/components\/auth-forms";\n/, "")
|
|
||||||
.replace(/<AuthForms \/>/, "");
|
|
||||||
await fs.writeFile(indexRoutePath, updatedIndexRouteContent, "utf8");
|
|
||||||
|
|
||||||
await fs.remove(path.join(serverDir, "src/lib/auth.ts"));
|
|
||||||
|
|
||||||
const indexFilePath = path.join(serverDir, "src/index.ts");
|
|
||||||
const indexContent = await fs.readFile(indexFilePath, "utf8");
|
|
||||||
const updatedIndexContent = indexContent
|
|
||||||
.replace(/import { auth } from "\.\/lib\/auth";\n/, "")
|
|
||||||
.replace(
|
|
||||||
/app\.on\(\["POST", "GET"\], "\/api\/auth\/\*\*", \(c\) => auth\.handler\(c\.req\.raw\)\);\n\n/,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
await fs.writeFile(indexFilePath, updatedIndexContent, "utf8");
|
|
||||||
|
|
||||||
const contextFilePath = path.join(serverDir, "src/lib/context.ts");
|
|
||||||
const contextContent = await fs.readFile(contextFilePath, "utf8");
|
|
||||||
const updatedContextContent = contextContent
|
|
||||||
.replace(/import { auth } from "\.\/auth";\n/, "")
|
|
||||||
.replace(
|
|
||||||
/const session = await auth\.api\.getSession\({\n\s+headers: hono\.req\.raw\.headers,\n\s+}\);/,
|
|
||||||
"const session = null;",
|
|
||||||
);
|
|
||||||
await fs.writeFile(contextFilePath, updatedContextContent, "utf8");
|
|
||||||
} else if (!hasDatabase) {
|
|
||||||
log.warn(
|
log.warn(
|
||||||
pc.yellow(
|
pc.yellow(
|
||||||
"Authentication enabled but no database selected. Auth will not function properly.",
|
"Authentication enabled but no database selected. Auth will not function properly.",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
const envPath = path.join(serverDir, ".env");
|
}
|
||||||
const templateEnvPath = path.join(
|
|
||||||
PKG_ROOT,
|
|
||||||
getOrmTemplatePath(
|
|
||||||
options.orm,
|
|
||||||
options.database,
|
|
||||||
"packages/server/_env",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(envPath))) {
|
const envPath = path.join(serverDir, ".env");
|
||||||
if (await fs.pathExists(templateEnvPath)) {
|
const templateEnvPath = path.join(
|
||||||
await fs.copy(templateEnvPath, envPath);
|
PKG_ROOT,
|
||||||
} else {
|
getOrmTemplatePath(options.orm, options.database, "packages/server/_env"),
|
||||||
const defaultEnv = `BETTER_AUTH_SECRET=${generateAuthSecret()}
|
);
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(envPath))) {
|
||||||
|
if (await fs.pathExists(templateEnvPath)) {
|
||||||
|
await fs.copy(templateEnvPath, envPath);
|
||||||
|
} else {
|
||||||
|
const defaultEnv = `BETTER_AUTH_SECRET=${generateAuthSecret()}
|
||||||
BETTER_AUTH_URL=http://localhost:3000
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
CORS_ORIGIN=http://localhost:3001
|
CORS_ORIGIN=http://localhost:3001
|
||||||
${options.database === "sqlite" ? "TURSO_CONNECTION_URL=http://127.0.0.1:8080" : ""}
|
${options.database === "sqlite" ? "TURSO_CONNECTION_URL=http://127.0.0.1:8080" : ""}
|
||||||
${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
|
${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""}
|
||||||
`;
|
`;
|
||||||
await fs.writeFile(envPath, defaultEnv);
|
await fs.writeFile(envPath, defaultEnv);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let envContent = await fs.readFile(envPath, "utf8");
|
let envContent = await fs.readFile(envPath, "utf8");
|
||||||
|
|
||||||
if (!envContent.includes("BETTER_AUTH_SECRET")) {
|
if (!envContent.includes("BETTER_AUTH_SECRET")) {
|
||||||
envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`;
|
envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`;
|
||||||
}
|
|
||||||
|
|
||||||
if (!envContent.includes("BETTER_AUTH_URL")) {
|
|
||||||
envContent += "\nBETTER_AUTH_URL=http://localhost:3000";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!envContent.includes("CORS_ORIGIN")) {
|
|
||||||
envContent += "\nCORS_ORIGIN=http://localhost:3001";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
options.database === "sqlite" &&
|
|
||||||
!envContent.includes("TURSO_CONNECTION_URL")
|
|
||||||
) {
|
|
||||||
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
|
||||||
envContent += '\nDATABASE_URL="file:./dev.db"';
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.writeFile(envPath, envContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientEnvPath = path.join(clientDir, ".env");
|
if (!envContent.includes("BETTER_AUTH_URL")) {
|
||||||
if (!(await fs.pathExists(clientEnvPath))) {
|
envContent += "\nBETTER_AUTH_URL=http://localhost:3000";
|
||||||
const clientEnvContent = "VITE_SERVER_URL=http://localhost:3000\n";
|
|
||||||
await fs.writeFile(clientEnvPath, clientEnvContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.orm === "prisma") {
|
if (!envContent.includes("CORS_ORIGIN")) {
|
||||||
const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
envContent += "\nCORS_ORIGIN=http://localhost:3001";
|
||||||
const defaultPrismaAuthPath = path.join(
|
}
|
||||||
PKG_ROOT,
|
|
||||||
getOrmTemplatePath(
|
|
||||||
options.orm,
|
|
||||||
options.database,
|
|
||||||
"packages/server/src/lib/auth.ts",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await fs.pathExists(defaultPrismaAuthPath)) &&
|
options.database === "sqlite" &&
|
||||||
!(await fs.pathExists(prismaAuthPath))
|
!envContent.includes("TURSO_CONNECTION_URL")
|
||||||
) {
|
) {
|
||||||
await fs.ensureDir(path.dirname(prismaAuthPath));
|
envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080";
|
||||||
await fs.copy(defaultPrismaAuthPath, prismaAuthPath);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let authContent = await fs.readFile(prismaAuthPath, "utf8");
|
if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) {
|
||||||
if (!authContent.includes("trustedOrigins")) {
|
envContent += '\nDATABASE_URL="file:./dev.db"';
|
||||||
authContent = authContent.replace(
|
}
|
||||||
"export const auth = betterAuth({",
|
|
||||||
"export const auth = betterAuth({\n trustedOrigins: [process.env.CORS_ORIGIN!],",
|
|
||||||
);
|
|
||||||
await fs.writeFile(prismaAuthPath, authContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageJsonPath = path.join(projectDir, "package.json");
|
await fs.writeFile(envPath, envContent);
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
}
|
||||||
const packageJson = await fs.readJson(packageJsonPath);
|
|
||||||
|
|
||||||
packageJson.scripts["prisma:generate"] =
|
const clientEnvPath = path.join(clientDir, ".env");
|
||||||
"cd packages/server && npx prisma generate";
|
if (!(await fs.pathExists(clientEnvPath))) {
|
||||||
packageJson.scripts["prisma:push"] =
|
const clientEnvContent = "VITE_SERVER_URL=http://localhost:3000\n";
|
||||||
"cd packages/server && npx prisma db push";
|
await fs.writeFile(clientEnvPath, clientEnvContent);
|
||||||
packageJson.scripts["prisma:studio"] =
|
}
|
||||||
"cd packages/server && npx prisma studio";
|
|
||||||
packageJson.scripts["db:setup"] =
|
|
||||||
"npm run auth:generate && npm run prisma:generate && npm run prisma:push";
|
|
||||||
|
|
||||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
if (options.orm === "prisma") {
|
||||||
}
|
const packageJsonPath = path.join(projectDir, "package.json");
|
||||||
} else if (options.orm === "drizzle") {
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts");
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
const defaultDrizzleAuthPath = path.join(
|
|
||||||
PKG_ROOT,
|
|
||||||
getOrmTemplatePath(
|
|
||||||
options.orm,
|
|
||||||
options.database,
|
|
||||||
"packages/server/src/lib/auth.ts",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
packageJson.scripts["prisma:generate"] =
|
||||||
(await fs.pathExists(defaultDrizzleAuthPath)) &&
|
"cd packages/server && npx prisma generate";
|
||||||
!(await fs.pathExists(drizzleAuthPath))
|
packageJson.scripts["prisma:push"] =
|
||||||
) {
|
"cd packages/server && npx prisma db push";
|
||||||
await fs.ensureDir(path.dirname(drizzleAuthPath));
|
packageJson.scripts["prisma:studio"] =
|
||||||
await fs.copy(defaultDrizzleAuthPath, drizzleAuthPath);
|
"cd packages/server && npx prisma studio";
|
||||||
}
|
packageJson.scripts["db:setup"] =
|
||||||
|
"npm run auth:generate && npm run prisma:generate && npm run prisma:push";
|
||||||
|
|
||||||
const packageJsonPath = path.join(projectDir, "package.json");
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
}
|
||||||
const packageJson = await fs.readJson(packageJsonPath);
|
} else if (options.orm === "drizzle") {
|
||||||
|
const packageJsonPath = path.join(projectDir, "package.json");
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
packageJson.scripts["db:push"] =
|
packageJson.scripts["db:push"] =
|
||||||
"cd packages/server && npx @better-auth/cli migrate";
|
"cd packages/server && npx @better-auth/cli migrate";
|
||||||
packageJson.scripts["db:setup"] =
|
packageJson.scripts["db:setup"] =
|
||||||
"npm run auth:generate && npm run db:push";
|
"npm run auth:generate && npm run db:push";
|
||||||
|
|
||||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import pc from "picocolors";
|
|||||||
import { PKG_ROOT } from "../constants";
|
import { PKG_ROOT } from "../constants";
|
||||||
import type { ProjectConfig } from "../types";
|
import type { ProjectConfig } from "../types";
|
||||||
import { setupAddons } from "./addons-setup";
|
import { setupAddons } from "./addons-setup";
|
||||||
import { configureAuth } from "./auth-setup";
|
import { setupAuth } from "./auth-setup";
|
||||||
import { createReadme } from "./create-readme";
|
import { createReadme } from "./create-readme";
|
||||||
import { setupDatabase } from "./db-setup";
|
import { setupDatabase } from "./db-setup";
|
||||||
import { displayPostInstallInstructions } from "./post-installation";
|
import { displayPostInstallInstructions } from "./post-installation";
|
||||||
@@ -24,6 +24,13 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
}
|
}
|
||||||
await fs.copy(templateDir, projectDir);
|
await fs.copy(templateDir, projectDir);
|
||||||
|
|
||||||
|
if (options.auth) {
|
||||||
|
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
|
||||||
|
if (await fs.pathExists(authTemplateDir)) {
|
||||||
|
await fs.copy(authTemplateDir, projectDir, { overwrite: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.orm !== "none" && options.database !== "none") {
|
if (options.orm !== "none" && options.database !== "none") {
|
||||||
const ormTemplateDir = path.join(
|
const ormTemplateDir = path.join(
|
||||||
PKG_ROOT,
|
PKG_ROOT,
|
||||||
@@ -40,6 +47,10 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
path.join(projectDir, "packages/server/_env"),
|
path.join(projectDir, "packages/server/_env"),
|
||||||
path.join(projectDir, "packages/server/.env"),
|
path.join(projectDir, "packages/server/.env"),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
path.join(projectDir, "packages/client/_env"),
|
||||||
|
path.join(projectDir, "packages/client/.env"),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const [source, target] of envFiles) {
|
for (const [source, target] of envFiles) {
|
||||||
@@ -58,7 +69,8 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
options.orm,
|
options.orm,
|
||||||
options.turso ?? options.database === "sqlite",
|
options.turso ?? options.database === "sqlite",
|
||||||
);
|
);
|
||||||
await configureAuth(
|
|
||||||
|
await setupAuth(
|
||||||
projectDir,
|
projectDir,
|
||||||
options.auth,
|
options.auth,
|
||||||
options.database !== "none",
|
options.database !== "none",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { ModeToggle } from "./mode-toggle";
|
import { ModeToggle } from "./mode-toggle";
|
||||||
import UserMenu from "./user-menu";
|
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return (
|
return (
|
||||||
@@ -16,27 +15,9 @@ export default function Header() {
|
|||||||
>
|
>
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
|
||||||
to="/dashboard"
|
|
||||||
activeProps={{
|
|
||||||
className: "font-bold",
|
|
||||||
}}
|
|
||||||
activeOptions={{ exact: true }}
|
|
||||||
>
|
|
||||||
Dashboard
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/about"
|
|
||||||
activeProps={{
|
|
||||||
className: "font-bold",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
About
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
<UserMenu />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/about")({
|
|
||||||
component: AboutComponent,
|
|
||||||
});
|
|
||||||
|
|
||||||
function AboutComponent() {
|
|
||||||
return (
|
|
||||||
<div className="p-2">
|
|
||||||
<h3>About</h3>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import AuthForms from "@/components/auth-forms";
|
|
||||||
import { trpc } from "@/utils/trpc";
|
import { trpc } from "@/utils/trpc";
|
||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ function HomeComponent() {
|
|||||||
<h3>Welcome Home!</h3>
|
<h3>Welcome Home!</h3>
|
||||||
<Link to="/dashboard">Go to Dashboard</Link>
|
<Link to="/dashboard">Go to Dashboard</Link>
|
||||||
<p>healthCheck: {healthCheck.data}</p>
|
<p>healthCheck: {healthCheck.data}</p>
|
||||||
<AuthForms />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import "dotenv/config";
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
import { logger } from "hono/logger";
|
import { logger } from "hono/logger";
|
||||||
import { auth } from "./lib/auth";
|
|
||||||
import { createContext } from "./lib/context";
|
import { createContext } from "./lib/context";
|
||||||
import { appRouter } from "./routers/index";
|
import { appRouter } from "./routers/index";
|
||||||
|
|
||||||
@@ -17,13 +16,9 @@ app.use(
|
|||||||
cors({
|
cors({
|
||||||
origin: process.env.CORS_ORIGIN!,
|
origin: process.env.CORS_ORIGIN!,
|
||||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||||
allowHeaders: ["Content-Type", "Authorization"],
|
|
||||||
credentials: true,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/trpc/*",
|
"/trpc/*",
|
||||||
trpcServer({
|
trpcServer({
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import type { Context as HonoContext } from "hono";
|
import type { Context as HonoContext } from "hono";
|
||||||
import { auth } from "./auth";
|
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
export type CreateContextOptions = {
|
||||||
hono: HonoContext;
|
hono: HonoContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
export async function createContext({ hono }: CreateContextOptions) {
|
||||||
const session = await auth.api.getSession({
|
|
||||||
headers: hono.req.raw.headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
session: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,3 @@ export const t = initTRPC.context<Context>().create();
|
|||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
|
|
||||||
export const publicProcedure = t.procedure;
|
export const publicProcedure = t.procedure;
|
||||||
|
|
||||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Authentication required",
|
|
||||||
cause: "No session",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
session: ctx.session,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import { router, publicProcedure, protectedProcedure } from "../lib/trpc";
|
import { router, publicProcedure } from "../lib/trpc";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
healthCheck: publicProcedure.query(() => {
|
healthCheck: publicProcedure.query(() => {
|
||||||
return "OK";
|
return "OK";
|
||||||
}),
|
}),
|
||||||
privateData: protectedProcedure.query(({ ctx }) => {
|
|
||||||
return {
|
|
||||||
message: "This is private",
|
|
||||||
user: ctx.session.user,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { ModeToggle } from "./mode-toggle";
|
||||||
|
import UserMenu from "./user-menu";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-row items-center justify-between px-2 py-1">
|
||||||
|
<div className="flex gap-4 text-lg">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
activeProps={{
|
||||||
|
className: "font-bold",
|
||||||
|
}}
|
||||||
|
activeOptions={{ exact: true }}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/dashboard"
|
||||||
|
activeProps={{
|
||||||
|
className: "font-bold",
|
||||||
|
}}
|
||||||
|
activeOptions={{ exact: true }}
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<ModeToggle />
|
||||||
|
<UserMenu />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import AuthForms from "@/components/auth-forms";
|
||||||
|
import { trpc } from "@/utils/trpc";
|
||||||
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/")({
|
||||||
|
component: HomeComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function HomeComponent() {
|
||||||
|
const healthCheck = trpc.healthCheck.useQuery();
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<h3>Welcome Home!</h3>
|
||||||
|
<Link to="/dashboard">Go to Dashboard</Link>
|
||||||
|
<p>healthCheck: {healthCheck.data}</p>
|
||||||
|
<AuthForms />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
apps/cli/template/with-auth/packages/server/src/index.ts
Normal file
47
apps/cli/template/with-auth/packages/server/src/index.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { serve } from "@hono/node-server";
|
||||||
|
import { trpcServer } from "@hono/trpc-server";
|
||||||
|
import "dotenv/config";
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { cors } from "hono/cors";
|
||||||
|
import { logger } from "hono/logger";
|
||||||
|
import { auth } from "./lib/auth";
|
||||||
|
import { createContext } from "./lib/context";
|
||||||
|
import { appRouter } from "./routers/index";
|
||||||
|
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
app.use(logger());
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
"/*",
|
||||||
|
cors({
|
||||||
|
origin: process.env.CORS_ORIGIN!,
|
||||||
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||||
|
allowHeaders: ["Content-Type", "Authorization"],
|
||||||
|
credentials: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
"/trpc/*",
|
||||||
|
trpcServer({
|
||||||
|
router: appRouter,
|
||||||
|
createContext: (_opts, hono) => {
|
||||||
|
return createContext({ hono });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get("/healthCheck", (c) => {
|
||||||
|
return c.text("OK");
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
console.log(`Server is running on http://localhost:${port}`);
|
||||||
|
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
port,
|
||||||
|
});
|
||||||
24
apps/cli/template/with-auth/packages/server/src/lib/trpc.ts
Normal file
24
apps/cli/template/with-auth/packages/server/src/lib/trpc.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
|
import type { Context } from "./context";
|
||||||
|
|
||||||
|
export const t = initTRPC.context<Context>().create();
|
||||||
|
|
||||||
|
export const router = t.router;
|
||||||
|
|
||||||
|
export const publicProcedure = t.procedure;
|
||||||
|
|
||||||
|
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
||||||
|
if (!ctx.session) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "Authentication required",
|
||||||
|
cause: "No session",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
...ctx,
|
||||||
|
session: ctx.session,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import { router, publicProcedure, protectedProcedure } from "../lib/trpc";
|
||||||
|
|
||||||
|
export const appRouter = router({
|
||||||
|
healthCheck: publicProcedure.query(() => {
|
||||||
|
return "OK";
|
||||||
|
}),
|
||||||
|
privateData: protectedProcedure.query(({ ctx }) => {
|
||||||
|
return {
|
||||||
|
message: "This is private",
|
||||||
|
user: ctx.session.user,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppRouter = typeof appRouter;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Context as HonoContext } from "hono";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
|
||||||
|
export type CreateContextOptions = {
|
||||||
|
hono: HonoContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createContext({ hono }: CreateContextOptions) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: hono.req.raw.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Context as HonoContext } from "hono";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
|
||||||
|
export type CreateContextOptions = {
|
||||||
|
hono: HonoContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createContext({ hono }: CreateContextOptions) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: hono.req.raw.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Context as HonoContext } from "hono";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
|
||||||
|
export type CreateContextOptions = {
|
||||||
|
hono: HonoContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createContext({ hono }: CreateContextOptions) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: hono.req.raw.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Context as HonoContext } from "hono";
|
||||||
|
import { auth } from "./auth";
|
||||||
|
|
||||||
|
export type CreateContextOptions = {
|
||||||
|
hono: HonoContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createContext({ hono }: CreateContextOptions) {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
headers: hono.req.raw.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||||
Reference in New Issue
Block a user