From b34f45230c4f07e4e5073aedb020cec65d1a0dcc Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 12 May 2025 14:01:09 +0000 Subject: [PATCH 01/12] fix(deps): update dependency lucide-react to ^0.510.0 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2ec9912..a2f1584 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@radix-ui/react-tabs": "^1.1.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.509.0", + "lucide-react": "^0.510.0", "next": "15.3.2", "next-auth": "^5.0.0-beta.25", "next-themes": "^0.4.6", diff --git a/yarn.lock b/yarn.lock index 10b69f3..555c23a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4518,12 +4518,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.509.0": - version: 0.509.0 - resolution: "lucide-react@npm:0.509.0" +"lucide-react@npm:^0.510.0": + version: 0.510.0 + resolution: "lucide-react@npm:0.510.0" peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/eb7434a99738d2a3b3a193c4e0cf51691a06223ec8eda514ea7fd96cdbd311069d25353cd9e720810e5c7ba3891186de6a0463c3ac5382cc1d981938847b9b95 + checksum: 10c0/00b3efed64c370cd5ae97023583996ff83939baf6f4af11393d536ca9e520b0cc1a82a81aa6d0350625bff398daaa69357df74383631f2c3a0aeb6974f4237c7 languageName: node linkType: hard @@ -4597,7 +4597,7 @@ __metadata: eslint: "npm:9.26.0" eslint-config-next: "npm:15.3.2" eslint-config-prettier: "npm:10.1.5" - lucide-react: "npm:^0.509.0" + lucide-react: "npm:^0.510.0" next: "npm:15.3.2" next-auth: "npm:^5.0.0-beta.25" next-themes: "npm:^0.4.6" From 82201a1307a60744a4b08c309e5ec616e4cbe499 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:17:46 +0200 Subject: [PATCH 02/12] refactor: move auth check into pages --- src/app/page.tsx | 10 ++++++++-- src/auth.ts | 5 ++++- src/middleware.ts | 16 +--------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 7bcd29e..a86e576 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,9 @@ -export default function Home() { - return
; +import { auth } from '@/auth'; +import { redirect } from 'next/navigation'; + +export default async function Home() { + const session = await auth(); + + if (!session?.user) redirect('/login'); + else redirect('/home'); } diff --git a/src/auth.ts b/src/auth.ts index 50b654c..b3ed869 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -7,7 +7,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ ), callbacks: { authorized: async ({ auth }) => { - return !!auth; + return !!auth?.user; }, }, + pages: { + signIn: '/login', + }, }); diff --git a/src/middleware.ts b/src/middleware.ts index 175bedc..345d04d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,18 +1,4 @@ -import { auth } from '@/auth'; - -export default auth((req) => { - if ( - !req.auth && - req.nextUrl.pathname !== '/login' && - process.env.MEETUP_SKIP_LOGIN !== 'true' - ) { - const newUrl = new URL('/login', req.nextUrl.origin); - return Response.redirect(newUrl); - } else if (req.auth != null && req.nextUrl.pathname === '/') { - const newUrl = new URL('/home', req.nextUrl.origin); - return Response.redirect(newUrl); - } -}); +export { auth as middleware } from '@/auth'; export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], From 9c22252e0be318eb0b58c35c54bb5f91fabb0419 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:22:56 +0200 Subject: [PATCH 03/12] refactor: dynamically generated login page --- src/app/login/page.tsx | 14 +++++---- src/auth.ts | 47 +++++++++++++++++++++++++----- src/components/labeled-input.tsx | 9 +++--- src/components/user/login-form.tsx | 22 +++++++++++++- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 714e996..1786e82 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,4 +1,4 @@ -import { auth } from '@/auth'; +import { auth, providerMap } from '@/auth'; import SSOLogin from '@/components/user/sso-login-button'; import LoginForm from '@/components/user/login-form'; import { redirect } from 'next/navigation'; @@ -35,11 +35,15 @@ export default async function LoginPage() { -
+ {providerMap.length > 0 &&
} - {process.env.AUTH_AUTHENTIK_ISSUER && ( - - )} + {providerMap.map((provider) => ( + + ))}
diff --git a/src/auth.ts b/src/auth.ts index b3ed869..09a5065 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,16 +1,49 @@ import NextAuth from 'next-auth'; + +import type { Provider } from 'next-auth/providers'; +import Credentials from 'next-auth/providers/credentials'; + import Authentik from 'next-auth/providers/authentik'; +const providers: Provider[] = [ + !process.env.DISABLE_PASSWORD_LOGIN && + Credentials({ + credentials: { password: { label: 'Password', type: 'password' } }, + authorize(c) { + if (c.password !== 'password') return null; + return { + id: 'test', + name: 'Test User', + email: 'test@example.com', + }; + }, + }), + process.env.AUTH_AUTHENTIK_ID && Authentik, +].filter(Boolean) as Provider[]; + +export const providerMap = providers + .map((provider) => { + if (typeof provider === 'function') { + const providerData = provider(); + return { id: providerData.id, name: providerData.name }; + } else { + return { id: provider.id, name: provider.name }; + } + }) + .filter((provider) => provider.id !== 'credentials'); + export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [process.env.AUTH_AUTHENTIK_ISSUER ? Authentik : null].filter( - (x) => x !== null, - ), - callbacks: { - authorized: async ({ auth }) => { - return !!auth?.user; - }, + providers, + session: { + strategy: 'jwt', }, pages: { signIn: '/login', + signOut: '/logout', + }, + callbacks: { + authorized({ auth }) { + return !!auth?.user; + }, }, }); diff --git a/src/components/labeled-input.tsx b/src/components/labeled-input.tsx index 7b4768a..7a416d4 100644 --- a/src/components/labeled-input.tsx +++ b/src/components/labeled-input.tsx @@ -6,23 +6,24 @@ export default function LabeledInput({ label, placeholder, value, + name, }: { type: 'text' | 'email' | 'password'; label: string; placeholder?: string; value?: string; + name?: string; }) { - const elementId = Math.random().toString(36).substring(2, 15); - return (
- +
); diff --git a/src/components/user/login-form.tsx b/src/components/user/login-form.tsx index 20438e8..2869930 100644 --- a/src/components/user/login-form.tsx +++ b/src/components/user/login-form.tsx @@ -1,18 +1,38 @@ +import { signIn } from '@/auth'; import LabeledInput from '@/components/labeled-input'; import { Button } from '@/components/ui/button'; +import { AuthError } from 'next-auth'; +import { redirect } from 'next/navigation'; + +const SIGNIN_ERROR_URL = '/error'; export default function LoginForm() { return ( -
+ { + 'use server'; + try { + await signIn('credentials', formData); + } catch (error) { + if (error instanceof AuthError) { + return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`); + } + throw error; + } + }} + > + + + + + ); +} From bdbd71058c01933479ff16b110b5ed412ad5c8b7 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:27:18 +0200 Subject: [PATCH 05/12] docs: test user for development --- .env.example | 2 -- README.md | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index e509a47..6f53284 100644 --- a/.env.example +++ b/.env.example @@ -7,5 +7,3 @@ AUTH_AUTHENTIK_SECRET= AUTH_AUTHENTIK_ISSUER= NEXT_PUBLIC_APP_URL= - -MEETUP_SKIP_LOGIN= \ No newline at end of file diff --git a/README.md b/README.md index 8a71828..1fbb7bf 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,6 @@ This project is built with a modern tech stack: # Base URL of your application NEXT_PUBLIC_APP_URL="http://localhost:3000" - - # Development: Skip login flow (set to "true" to bypass authentication) - # Ensure this is NOT set to "true" in production. - MEETUP_SKIP_LOGIN="false" ``` 4. **Apply database migrations (Prisma):** @@ -111,11 +107,20 @@ This project is built with a modern tech stack: - (Optional: If you need to generate Prisma Client without running migrations, use `npx prisma generate`) 5. **Run the development server:** + ```bash yarn dev ``` + Open [http://localhost:3000](http://localhost:3000) in your browser to see the application. + The test user for the application is: + + ```bash + email: test@example.com + password: password + ``` + **Self-Hosting with Docker (Planned):** - A Docker image and `docker-compose.yml` file will be provided in the future to allow for easy self-hosting of the MeetUP application. This setup will also include database services. Instructions will be updated here once available. From c5b85aff7af31f7111959b01cc09ac22a014763e Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:29:05 +0200 Subject: [PATCH 06/12] refactor: removes unused sso logout button --- src/components/user/sso-logout-button.tsx | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/components/user/sso-logout-button.tsx diff --git a/src/components/user/sso-logout-button.tsx b/src/components/user/sso-logout-button.tsx deleted file mode 100644 index 84c4bae..0000000 --- a/src/components/user/sso-logout-button.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { signOut } from '@/auth'; -import { IconButton } from '@/components/icon-button'; -import { faDoorOpen } from '@fortawesome/free-solid-svg-icons'; - -export function Logout() { - return ( -
{ - 'use server'; - await signOut({ redirectTo: '/login' }); - }} - > - - Sign Out - -
- ); -} From fc828b2ac511915ca015b6d3cfe360bb78e2afbe Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 12 May 2025 20:00:50 +0000 Subject: [PATCH 07/12] chore(deps): update dependency @types/react to v19.1.4 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a2f1584..e9df94e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@eslint/eslintrc": "3.3.1", "@tailwindcss/postcss": "4.1.6", "@types/node": "22.15.17", - "@types/react": "19.1.3", + "@types/react": "19.1.4", "@types/react-dom": "19.1.4", "eslint": "9.26.0", "eslint-config-next": "15.3.2", diff --git a/yarn.lock b/yarn.lock index 555c23a..1371ee7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1863,12 +1863,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:19.1.3": - version: 19.1.3 - resolution: "@types/react@npm:19.1.3" +"@types/react@npm:19.1.4": + version: 19.1.4 + resolution: "@types/react@npm:19.1.4" dependencies: csstype: "npm:^3.0.2" - checksum: 10c0/f158f88871b8df1eeed637942d3e6142abcf505b617e4921ef3763b6d4f22241b9a883d864878dd2b6a2bdc8f4e7f871f24ef88f633d144a63257f4764b9478d + checksum: 10c0/501350d4f9cef13c5dd1b1496fa70ebaff52f6fa359b623b51c9d817e5bc4333fa3c8b7a6a4cbc88c643385052d66a243c3ceccfd6926062f917a2dd0535f6b3 languageName: node linkType: hard @@ -4590,7 +4590,7 @@ __metadata: "@radix-ui/react-tabs": "npm:^1.1.11" "@tailwindcss/postcss": "npm:4.1.6" "@types/node": "npm:22.15.17" - "@types/react": "npm:19.1.3" + "@types/react": "npm:19.1.4" "@types/react-dom": "npm:19.1.4" class-variance-authority: "npm:^0.7.1" clsx: "npm:^2.1.1" From bf6900f1153d74b4d9f5163bbb86bd22af31eb8c Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:17:46 +0200 Subject: [PATCH 08/12] refactor: move auth check into pages --- src/app/page.tsx | 10 ++++++++-- src/auth.ts | 5 ++++- src/middleware.ts | 16 +--------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 7bcd29e..a86e576 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,9 @@ -export default function Home() { - return
; +import { auth } from '@/auth'; +import { redirect } from 'next/navigation'; + +export default async function Home() { + const session = await auth(); + + if (!session?.user) redirect('/login'); + else redirect('/home'); } diff --git a/src/auth.ts b/src/auth.ts index 50b654c..b3ed869 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -7,7 +7,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ ), callbacks: { authorized: async ({ auth }) => { - return !!auth; + return !!auth?.user; }, }, + pages: { + signIn: '/login', + }, }); diff --git a/src/middleware.ts b/src/middleware.ts index 175bedc..345d04d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,18 +1,4 @@ -import { auth } from '@/auth'; - -export default auth((req) => { - if ( - !req.auth && - req.nextUrl.pathname !== '/login' && - process.env.MEETUP_SKIP_LOGIN !== 'true' - ) { - const newUrl = new URL('/login', req.nextUrl.origin); - return Response.redirect(newUrl); - } else if (req.auth != null && req.nextUrl.pathname === '/') { - const newUrl = new URL('/home', req.nextUrl.origin); - return Response.redirect(newUrl); - } -}); +export { auth as middleware } from '@/auth'; export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], From 09cb720571cd117c644203b22710e53f1fcb1931 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:22:56 +0200 Subject: [PATCH 09/12] refactor: dynamically generated login page --- src/app/login/page.tsx | 14 +++++---- src/auth.ts | 47 +++++++++++++++++++++++++----- src/components/labeled-input.tsx | 9 +++--- src/components/user/login-form.tsx | 22 +++++++++++++- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 714e996..1786e82 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,4 +1,4 @@ -import { auth } from '@/auth'; +import { auth, providerMap } from '@/auth'; import SSOLogin from '@/components/user/sso-login-button'; import LoginForm from '@/components/user/login-form'; import { redirect } from 'next/navigation'; @@ -35,11 +35,15 @@ export default async function LoginPage() { -
+ {providerMap.length > 0 &&
} - {process.env.AUTH_AUTHENTIK_ISSUER && ( - - )} + {providerMap.map((provider) => ( + + ))}
diff --git a/src/auth.ts b/src/auth.ts index b3ed869..09a5065 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,16 +1,49 @@ import NextAuth from 'next-auth'; + +import type { Provider } from 'next-auth/providers'; +import Credentials from 'next-auth/providers/credentials'; + import Authentik from 'next-auth/providers/authentik'; +const providers: Provider[] = [ + !process.env.DISABLE_PASSWORD_LOGIN && + Credentials({ + credentials: { password: { label: 'Password', type: 'password' } }, + authorize(c) { + if (c.password !== 'password') return null; + return { + id: 'test', + name: 'Test User', + email: 'test@example.com', + }; + }, + }), + process.env.AUTH_AUTHENTIK_ID && Authentik, +].filter(Boolean) as Provider[]; + +export const providerMap = providers + .map((provider) => { + if (typeof provider === 'function') { + const providerData = provider(); + return { id: providerData.id, name: providerData.name }; + } else { + return { id: provider.id, name: provider.name }; + } + }) + .filter((provider) => provider.id !== 'credentials'); + export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [process.env.AUTH_AUTHENTIK_ISSUER ? Authentik : null].filter( - (x) => x !== null, - ), - callbacks: { - authorized: async ({ auth }) => { - return !!auth?.user; - }, + providers, + session: { + strategy: 'jwt', }, pages: { signIn: '/login', + signOut: '/logout', + }, + callbacks: { + authorized({ auth }) { + return !!auth?.user; + }, }, }); diff --git a/src/components/labeled-input.tsx b/src/components/labeled-input.tsx index 7b4768a..7a416d4 100644 --- a/src/components/labeled-input.tsx +++ b/src/components/labeled-input.tsx @@ -6,23 +6,24 @@ export default function LabeledInput({ label, placeholder, value, + name, }: { type: 'text' | 'email' | 'password'; label: string; placeholder?: string; value?: string; + name?: string; }) { - const elementId = Math.random().toString(36).substring(2, 15); - return (
- +
); diff --git a/src/components/user/login-form.tsx b/src/components/user/login-form.tsx index 20438e8..2869930 100644 --- a/src/components/user/login-form.tsx +++ b/src/components/user/login-form.tsx @@ -1,18 +1,38 @@ +import { signIn } from '@/auth'; import LabeledInput from '@/components/labeled-input'; import { Button } from '@/components/ui/button'; +import { AuthError } from 'next-auth'; +import { redirect } from 'next/navigation'; + +const SIGNIN_ERROR_URL = '/error'; export default function LoginForm() { return ( -
+ { + 'use server'; + try { + await signIn('credentials', formData); + } catch (error) { + if (error instanceof AuthError) { + return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`); + } + throw error; + } + }} + > + + + + + ); +} From 3a36c42ed9c75527828125fa9feb9dfb7417b265 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:27:18 +0200 Subject: [PATCH 11/12] docs: test user for development --- .env.example | 2 -- README.md | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index e509a47..6f53284 100644 --- a/.env.example +++ b/.env.example @@ -7,5 +7,3 @@ AUTH_AUTHENTIK_SECRET= AUTH_AUTHENTIK_ISSUER= NEXT_PUBLIC_APP_URL= - -MEETUP_SKIP_LOGIN= \ No newline at end of file diff --git a/README.md b/README.md index 8a71828..1fbb7bf 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,6 @@ This project is built with a modern tech stack: # Base URL of your application NEXT_PUBLIC_APP_URL="http://localhost:3000" - - # Development: Skip login flow (set to "true" to bypass authentication) - # Ensure this is NOT set to "true" in production. - MEETUP_SKIP_LOGIN="false" ``` 4. **Apply database migrations (Prisma):** @@ -111,11 +107,20 @@ This project is built with a modern tech stack: - (Optional: If you need to generate Prisma Client without running migrations, use `npx prisma generate`) 5. **Run the development server:** + ```bash yarn dev ``` + Open [http://localhost:3000](http://localhost:3000) in your browser to see the application. + The test user for the application is: + + ```bash + email: test@example.com + password: password + ``` + **Self-Hosting with Docker (Planned):** - A Docker image and `docker-compose.yml` file will be provided in the future to allow for easy self-hosting of the MeetUP application. This setup will also include database services. Instructions will be updated here once available. From a7d58241006aeb04b8410f1a5549f9940193e857 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:29:05 +0200 Subject: [PATCH 12/12] refactor: removes unused sso logout button --- src/components/user/sso-logout-button.tsx | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/components/user/sso-logout-button.tsx diff --git a/src/components/user/sso-logout-button.tsx b/src/components/user/sso-logout-button.tsx deleted file mode 100644 index 84c4bae..0000000 --- a/src/components/user/sso-logout-button.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { signOut } from '@/auth'; -import { IconButton } from '@/components/icon-button'; -import { faDoorOpen } from '@fortawesome/free-solid-svg-icons'; - -export function Logout() { - return ( -
{ - 'use server'; - await signOut({ redirectTo: '/login' }); - }} - > - - Sign Out - -
- ); -}