From f61bdac45782dd06ea9f7ed6ec99b2a910b17260 Mon Sep 17 00:00:00 2001 From: dqnid Date: Sun, 21 Jul 2024 00:31:04 +0200 Subject: [PATCH] feat(front): basic next auth configuration done --- front/.env | 3 + front/.env.development | 1 + front/.env.production | 1 + front/package-lock.json | 158 +++++++++++++++++- front/package.json | 1 + front/src/app/(home)/layout.tsx | 1 + front/src/app/{ => (home)}/page.tsx | 2 + front/src/app/api/auth/[...nextauth]/route.ts | 6 + front/src/app/layout.tsx | 23 +-- .../auth/components/sign-in.widget.tsx | 30 ++++ .../src/modules/auth/configs/auth.options.ts | 52 ++++++ .../modules/auth/providers/auth.provider.tsx | 8 + front/src/modules/auth/types/next-auth.d.ts | 28 ++++ .../common/layouts/application.layout.tsx | 8 + .../modules/common/layouts/root.layout.tsx | 19 +++ .../common/providers/application.provider.tsx | 10 ++ front/tsconfig.json | 11 +- 17 files changed, 333 insertions(+), 29 deletions(-) create mode 100644 front/.env create mode 100644 front/.env.development create mode 100644 front/.env.production create mode 100644 front/src/app/(home)/layout.tsx rename front/src/app/{ => (home)}/page.tsx (64%) create mode 100644 front/src/app/api/auth/[...nextauth]/route.ts create mode 100644 front/src/modules/auth/components/sign-in.widget.tsx create mode 100644 front/src/modules/auth/configs/auth.options.ts create mode 100644 front/src/modules/auth/providers/auth.provider.tsx create mode 100644 front/src/modules/auth/types/next-auth.d.ts create mode 100644 front/src/modules/common/layouts/application.layout.tsx create mode 100644 front/src/modules/common/layouts/root.layout.tsx create mode 100644 front/src/modules/common/providers/application.provider.tsx diff --git a/front/.env b/front/.env new file mode 100644 index 0000000..2226642 --- /dev/null +++ b/front/.env @@ -0,0 +1,3 @@ +NEXTAUTH_URL=http://localhost:3000 +# GEN AUTH_SECRET: $ openssl rand -base64 32 +NEXTAUTH_SECRET=6lHRWUvCBtqlgTWc6aFn6s6PudYjuN6oUY+RrcEntTU= diff --git a/front/.env.development b/front/.env.development new file mode 100644 index 0000000..92fc50d --- /dev/null +++ b/front/.env.development @@ -0,0 +1 @@ +NEXTAUTH_URL=http://localhost:3000 diff --git a/front/.env.production b/front/.env.production new file mode 100644 index 0000000..2e4a53a --- /dev/null +++ b/front/.env.production @@ -0,0 +1 @@ +NEXTAUTH_URL=http://some-name.io:3000 diff --git a/front/package-lock.json b/front/package-lock.json index 253eae8..44f97d7 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -1,14 +1,15 @@ { - "name": "personal-finance-front", + "name": "front-next-archetype", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "personal-finance-front", + "name": "front-next-archetype", "version": "0.1.0", "dependencies": { "next": "14.2.3", + "next-auth": "^4.24.7", "react": "^18", "react-dom": "^18", "sass": "^1.77.4" @@ -26,7 +27,6 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -351,6 +351,15 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1024,6 +1033,15 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2729,6 +2747,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3002,6 +3029,34 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.7", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz", + "integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3010,6 +3065,12 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3019,6 +3080,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -3135,6 +3205,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3144,6 +3223,33 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz", + "integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.5", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3313,6 +3419,28 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3322,6 +3450,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3426,8 +3560,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", @@ -4158,6 +4291,15 @@ "punycode": "^2.1.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4361,6 +4503,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/front/package.json b/front/package.json index c7a3196..a03e3fb 100644 --- a/front/package.json +++ b/front/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "next": "14.2.3", + "next-auth": "^4.24.7", "react": "^18", "react-dom": "^18", "sass": "^1.77.4" diff --git a/front/src/app/(home)/layout.tsx b/front/src/app/(home)/layout.tsx new file mode 100644 index 0000000..7db596a --- /dev/null +++ b/front/src/app/(home)/layout.tsx @@ -0,0 +1 @@ +export { ApplicationLayout as default } from "@/modules/common/layouts/application.layout"; diff --git a/front/src/app/page.tsx b/front/src/app/(home)/page.tsx similarity index 64% rename from front/src/app/page.tsx rename to front/src/app/(home)/page.tsx index feaf32c..e6a1544 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/(home)/page.tsx @@ -1,3 +1,4 @@ +import { SignInWidget } from "@/modules/auth/components/sign-in.widget"; import ThemeSwitcher from "@/modules/common/theme-switcher"; export default function Home() { @@ -5,6 +6,7 @@ export default function Home() {
Main page +
); } diff --git a/front/src/app/api/auth/[...nextauth]/route.ts b/front/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..472443a --- /dev/null +++ b/front/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import { authOptions } from "@/modules/auth/configs/auth.options"; +import NextAuth from "next-auth"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/front/src/app/layout.tsx b/front/src/app/layout.tsx index 63930c3..ddb8b6a 100644 --- a/front/src/app/layout.tsx +++ b/front/src/app/layout.tsx @@ -1,22 +1 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "@/styles/main.scss"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Personal finance - Home", - description: "Manage your money blablabla", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} +export { RootLayout as default } from "@/modules/common/layouts/root.layout"; diff --git a/front/src/modules/auth/components/sign-in.widget.tsx b/front/src/modules/auth/components/sign-in.widget.tsx new file mode 100644 index 0000000..7610cf4 --- /dev/null +++ b/front/src/modules/auth/components/sign-in.widget.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { signIn, useSession } from "next-auth/react"; + +export const SignInWidget = () => { + const { data } = useSession(); + return ( +
+ + {JSON.stringify(data)} +
+ ); +}; diff --git a/front/src/modules/auth/configs/auth.options.ts b/front/src/modules/auth/configs/auth.options.ts new file mode 100644 index 0000000..7700ba5 --- /dev/null +++ b/front/src/modules/auth/configs/auth.options.ts @@ -0,0 +1,52 @@ +import { AuthOptions, User } from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; + +export const authOptions: AuthOptions = { + session: { + strategy: "jwt", + }, + pages: { + signIn: "/auth/sign-in", + }, + providers: [ + CredentialsProvider({ + name: "Sign in", + credentials: { + username: { label: "Username", type: "text", placeholder: "yourself" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials, req) { + console.log("__credentials", credentials); + const user: User = { + id: credentials?.password ?? "asdf", + role: "admin", + image: "none", + name: credentials?.username, + apiSession: { + accessToken: credentials?.password ?? "asdf", + }, + }; + return user; + }, + }), + ], + callbacks: { + async jwt({ token, user }) { + // Persist the OAuth access_token to the token right after signin + if (user) { + const { apiSession, ...cleanedUser } = user; + token.user = cleanedUser; + token.apiSession = apiSession; + } + return token; + }, + async session({ session, token }) { + // Send properties to the client, like an access_token from a provider. + if (token?.user && session) { + session.apiSession = token.apiSession; + session.user = token.user; + } + return session; + }, + }, +}; diff --git a/front/src/modules/auth/providers/auth.provider.tsx b/front/src/modules/auth/providers/auth.provider.tsx new file mode 100644 index 0000000..2ad16f0 --- /dev/null +++ b/front/src/modules/auth/providers/auth.provider.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { SessionProvider } from "next-auth/react"; +import { PropsWithChildren } from "react"; + +export const AuthProvider: React.FC = ({ children }) => { + return {children}; +}; diff --git a/front/src/modules/auth/types/next-auth.d.ts b/front/src/modules/auth/types/next-auth.d.ts new file mode 100644 index 0000000..18bc6af --- /dev/null +++ b/front/src/modules/auth/types/next-auth.d.ts @@ -0,0 +1,28 @@ +import NextAuth, { DefaultSession } from "next-auth"; +import { JWT, DefaultJWT } from "next-auth/jwt"; + +declare module "next-auth" { + type Role = "user" | "admin"; + interface ApiSession { + accessToken: string; + refreshToken?: string; + } + interface User { + id: string; + role: Role; + image?: string; + name?: string; + apiSession?: ApiSession; + } + + interface Session extends DefaultSession { + apiSession?: ApiSession; + } +} + +declare module "next-auth/jwt" { + interface JWT { + user?: Omit; + apiSession?: ApiSession; + } +} diff --git a/front/src/modules/common/layouts/application.layout.tsx b/front/src/modules/common/layouts/application.layout.tsx new file mode 100644 index 0000000..fb729b9 --- /dev/null +++ b/front/src/modules/common/layouts/application.layout.tsx @@ -0,0 +1,8 @@ +import { PropsWithChildren } from "react"; +import { ApplicationProvider } from "../providers/application.provider"; + +export const ApplicationLayout: React.FC = ({ + children, +}) => { + return {children}; +}; diff --git a/front/src/modules/common/layouts/root.layout.tsx b/front/src/modules/common/layouts/root.layout.tsx new file mode 100644 index 0000000..5a707f2 --- /dev/null +++ b/front/src/modules/common/layouts/root.layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; +import "@/styles/main.scss"; + +export const metadata: Metadata = { + title: "Personal finance - Home", + description: "Manage your money blablabla", +}; + +export function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/front/src/modules/common/providers/application.provider.tsx b/front/src/modules/common/providers/application.provider.tsx new file mode 100644 index 0000000..d6415c9 --- /dev/null +++ b/front/src/modules/common/providers/application.provider.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { AuthProvider } from "@/modules/auth/providers/auth.provider"; +import { PropsWithChildren } from "react"; + +export const ApplicationProvider: React.FC = ({ + children, +}) => { + return {children}; +}; diff --git a/front/tsconfig.json b/front/tsconfig.json index 7b28589..bfe478e 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -19,8 +19,15 @@ ], "paths": { "@/*": ["./src/*"] - } + }, + "typeRoots": ["./src/modules/auth/types"] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "src/modules/**/*.d.ts" + ], "exclude": ["node_modules"] }