Skip to content

Commit

Permalink
next-auth implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
agnlez committed Oct 7, 2024
1 parent 788e105 commit 9439305
Show file tree
Hide file tree
Showing 19 changed files with 572 additions and 67 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-prefix=''
3 changes: 3 additions & 0 deletions client/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEXTAUTH_URL=http://localhost:$PORT
NEXTAUTH_SECRET=WAzjpS46vFxp17TsRDU3FXo+TF0vrfy6uhCXwGMBUE8=
NEXT_PUBLIC_API_URL=http://localhost:4000
107 changes: 105 additions & 2 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,106 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"next/core-web-vitals",
"next/typescript"
],
"rules": {
"no-console": [1, { "allow": ["info", "error", "debug"] }],
"import/order": [
"warn",
{
"groups": ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"pathGroups": [
{
"pattern": "react",
"group": "builtin",
"position": "before"
},
{
"pattern": "react**",
"group": "builtin"
},
{
"pattern": "@react**",
"group": "builtin"
},
{
"pattern": "next/**",
"group": "builtin",
"position": "after"
},
{
"pattern": "node_modules/**",
"group": "builtin"
},
{
"pattern": "@/env.mjs",
"group": "internal",
"position": "before"
},
{
"pattern": "@/lib/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/data/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/store",
"group": "internal",
"position": "before"
},
{
"pattern": "@/store/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/types/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/app/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/hooks/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/constants/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/containers/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/components/**",
"group": "internal"
},
{
"pattern": "@/services/**",
"group": "internal",
"position": "after"
}
],
"pathGroupsExcludedImportTypes": ["react"]
}
]
}
}
5 changes: 5 additions & 0 deletions client/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"semi": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
35 changes: 0 additions & 35 deletions client/app/layout.tsx

This file was deleted.

10 changes: 0 additions & 10 deletions client/lib/queryClient.ts

This file was deleted.

31 changes: 31 additions & 0 deletions client/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextResponse } from "next/server";

import { NextRequestWithAuth, withAuth } from "next-auth/middleware";

const PRIVATE_PAGES = /^(\/profile)/;

export default function middleware(req: NextRequestWithAuth) {
if (PRIVATE_PAGES.test(req.nextUrl.pathname)) {
return withAuth(req, {
pages: {
signIn: "/auth/signin",
},
});
}

return NextResponse.next();
}

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - health (health check endpoint)
*/
"/((?!api|_next/static|_next/image|favicon.ico|health).*)",
],
};
6 changes: 5 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"lint": "next lint"
},
"dependencies": {
"@ts-rest/react-query": "^3.51.0",
"@tanstack/react-query": "5.59.0",
"@ts-rest/react-query": "3.51.0",
"next": "14.2.10",
"next-auth": "4.24.8",
"react": "^18",
"react-dom": "^18"
},
Expand All @@ -21,6 +23,8 @@
"eslint": "^8",
"eslint-config-next": "14.2.8",
"postcss": "^8",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
"tailwindcss": "^3.4.1",
"typescript": "catalog:"
}
Expand Down
96 changes: 96 additions & 0 deletions client/src/app/auth/api/[...nextauth]/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { UserWithAccessToken } from "@shared/dtos/user.dto";
import { LogInSchema } from "@shared/schemas/auth/login.schema";
import type {
GetServerSidePropsContext,
NextApiRequest,
NextApiResponse,
} from "next";
import { getServerSession, NextAuthOptions } from "next-auth";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { JWT } from "next-auth/jwt";
import Credentials from "next-auth/providers/credentials";

import { client } from "@/lib/queryClient";

declare module "next-auth" {
interface Session {
user: UserWithAccessToken["user"];
accessToken: UserWithAccessToken["accessToken"];
}

interface User extends UserWithAccessToken {}
}

declare module "next-auth/jwt" {
interface JWT {
user: UserWithAccessToken["user"];
accessToken: UserWithAccessToken["accessToken"];
}
}

export const config = {
providers: [
Credentials({
// @ts-expect-error - why is so hard to type this?
authorize: async (credentials) => {
let access: UserWithAccessToken | null = null;

const { email, password } = await LogInSchema.parseAsync(credentials);

const response = await client.auth.login.mutation({
body: {
email,
password,
},
});

if (response.status === 201) {
access = response.body;
}

if (!access) {
if (response.status === 401) {
throw new Error(
response.body.errors?.[0]?.title || "unknown error",
);
}
}

return access;
},
}),
],
callbacks: {
jwt({ token, user: access, trigger, session }) {
if (access) {
token.user = access.user;
token.accessToken = access.accessToken;
}

if (trigger === "update") {
token.user.email = session.email;
}

return token;
},
session({ session, token }) {
return {
...session,
user: token.user,
accessToken: token.accessToken,
};
},
},
pages: {
signIn: "/auth/signin",
},
} as NextAuthOptions;

export function auth(
...args:
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]]
| [NextApiRequest, NextApiResponse]
| []
) {
return getServerSession(...args, config);
}
7 changes: 7 additions & 0 deletions client/src/app/auth/api/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import NextAuth from "next-auth";

import { config } from "./config";

const handler = NextAuth(config);

export { handler as GET, handler as POST };
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
34 changes: 34 additions & 0 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Inter } from "next/font/google";

import type { Metadata } from "next";
import "@/app/globals.css";
import { getServerSession } from "next-auth";

import { config } from "@/app/auth/api/[...nextauth]/config";

import LayoutProviders from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Blue Carbon Cost",
description: "[TBD]",
};

export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(config);

return (
<LayoutProviders session={session}>
<html lang="en">
<body className={inter.className}>
<main>{children}</main>
</body>
</html>
</LayoutProviders>
);
}
Loading

0 comments on commit 9439305

Please sign in to comment.