Configured Azure AD and Dockerfile

This commit is contained in:
Ahmed Darrazi 2025-12-05 21:00:49 +01:00
parent 85f20147ed
commit ad262a315c
6 changed files with 103 additions and 53 deletions

View File

@ -1,36 +1,47 @@
# 1. Stage: Abhängigkeiten installieren # 1. Stage: Abhängigkeiten installieren
FROM node:20-alpine AS deps FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json* ./ COPY package.json package-lock.json* ./
RUN npm ci RUN npm ci
# 2. Stage: Builder (Der Code wird kompiliert) # 2. Stage: Builder (Code kompilieren)
FROM node:20-alpine AS builder FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
# Hier werden auch die Environment Variables für den Build gebraucht
# (In Dokploy setzt du diese später, aber für den Build ignorieren wir ESLint Fehler oft) ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build RUN npm run build
# 3. Stage: Runner (Das eigentliche Image für Dokploy - winzig klein) # 3. Stage: Runner (Production Image)
FROM node:20-alpine AS runner FROM node:20-alpine AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1 ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
# Wir kopieren nur das Nötigste aus dem Builder
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
# Set correct permissions for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Kopiere den Standalone-Build und Static-Files
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs USER nextjs
EXPOSE 3000 EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0" ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"] CMD ["node", "server.js"]

View File

@ -1,76 +1,80 @@
@tailwind base; @import "tailwindcss";
@tailwind components;
@tailwind utilities; @theme {
--color-background: hsl(0 0% 100%);
--color-foreground: hsl(0 0% 3.9%);
--color-card: hsl(0 0% 100%);
--color-card-foreground: hsl(0 0% 3.9%);
--color-popover: hsl(0 0% 100%);
--color-popover-foreground: hsl(0 0% 3.9%);
--color-primary: hsl(0 0% 9%);
--color-primary-foreground: hsl(0 0% 98%);
--color-secondary: hsl(0 0% 96.1%);
--color-secondary-foreground: hsl(0 0% 9%);
--color-muted: hsl(0 0% 96.1%);
--color-muted-foreground: hsl(0 0% 45.1%);
--color-accent: hsl(0 0% 96.1%);
--color-accent-foreground: hsl(0 0% 9%);
--color-destructive: hsl(0 84.2% 60.2%);
--color-destructive-foreground: hsl(0 0% 98%);
--color-border: hsl(0 0% 89.8%);
--color-input: hsl(0 0% 89.8%);
--color-ring: hsl(0 0% 3.9%);
--radius: 0.5rem;
}
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 0 0% 3.9%; --foreground: 0 0% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 0 0% 3.9%; --card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%; --popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%; --primary: 0 0% 9%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%; --secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%; --muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%; --muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%; --accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%; --accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%; --border: 0 0% 89.8%;
--input: 0 0% 89.8%; --input: 0 0% 89.8%;
--ring: 0 0% 3.9%; --ring: 0 0% 3.9%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 0 0% 3.9%; --background: 0 0% 3.9%;
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 0 0% 3.9%; --card: 0 0% 3.9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%; --popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 0 0% 98%; --primary: 0 0% 98%;
--primary-foreground: 0 0% 9%; --primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%; --secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%; --muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%; --muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%; --accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%; --accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%; --border: 0 0% 14.9%;
--input: 0 0% 14.9%; --input: 0 0% 14.9%;
--ring: 0 0% 83.1%; --ring: 0 0% 83.1%;
} }
}
@layer base {
* { * {
@apply border-border; border-color: hsl(var(--border));
} }
body { body {
@apply bg-background text-foreground; background-color: hsl(var(--background));
color: hsl(var(--foreground));
} }
} }

View File

@ -24,7 +24,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en" suppressHydrationWarning>
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >

View File

@ -3,14 +3,15 @@ import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { DefaultSession, getServerSession, NextAuthOptions } from "next-auth"; import { DefaultSession, getServerSession, NextAuthOptions } from "next-auth";
import { Adapter } from "next-auth/adapters"; import { Adapter } from "next-auth/adapters";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { env } from "@/lib/env.mjs" import { z } from "zod";
import AzureADProvider from "next-auth/providers/azure-ad";
declare module "next-auth" { declare module "next-auth" {
interface Session { interface Session {
user: DefaultSession["user"] & { user: DefaultSession["user"] & {
id: string; id: string;
}; };
accessToken?: string;
} }
} }
@ -24,16 +25,43 @@ export type AuthSession = {
} | null; } | null;
}; };
const envSchema = z.object({
AZURE_AD_CLIENT_ID: z.string().min(1),
AZURE_AD_CLIENT_SECRET: z.string().min(1),
});
export const env = envSchema.parse(process.env);
export const authOptions: NextAuthOptions = { export const authOptions: NextAuthOptions = {
adapter: DrizzleAdapter(db) as Adapter, adapter: DrizzleAdapter(db) as Adapter,
callbacks: { callbacks: {
session: ({ session, user }) => { jwt: async ({ token, account }) => {
session.user.id = user.id; if (account) {
token.accessToken = account.access_token;
}
return token;
},
session: ({ session, token, user }) => {
if (user) {
session.user.id = user.id;
}
if (token?.accessToken) {
session.accessToken = token.accessToken as string;
}
return session; return session;
}, },
}, },
providers: [ providers: [
AzureADProvider({
clientId: env.AZURE_AD_CLIENT_ID,
clientSecret: env.AZURE_AD_CLIENT_SECRET,
tenantId: "common", // Multi-Tenancy Support
authorization: {
params: {
scope: "openid profile email offline_access User.Read",
},
},
}),
], ],
}; };

View File

@ -11,7 +11,7 @@ export const env = createEnv({
NEXTAUTH_SECRET: process.env.NODE_ENV === "production" NEXTAUTH_SECRET: process.env.NODE_ENV === "production"
? z.string().min(1) ? z.string().min(1)
: z.string().min(1).optional(), : z.string().optional(),
NEXTAUTH_URL: z.preprocess( NEXTAUTH_URL: z.preprocess(
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
// Since NextAuth.js automatically uses the VERCEL_URL if present. // Since NextAuth.js automatically uses the VERCEL_URL if present.
@ -19,15 +19,21 @@ export const env = createEnv({
// VERCEL_URL doesn't include `https` so it cant be validated as a URL // VERCEL_URL doesn't include `https` so it cant be validated as a URL
process.env.VERCEL_URL ? z.string().min(1) : z.string().url() process.env.VERCEL_URL ? z.string().min(1) : z.string().url()
), ),
RESEND_API_KEY: z.string().min(1),
STRIPE_SECRET_KEY: z.string().min(1), // Azure AD (Microsoft Entra ID) - optional in development
STRIPE_WEBHOOK_SECRET: z.string().min(1), AZURE_AD_CLIENT_ID: z.string().optional(),
AZURE_AD_CLIENT_SECRET: z.string().optional(),
// Optional in development
RESEND_API_KEY: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
}, },
client: { client: {
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().optional(),
NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: z.string().min(1), NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: z.string().optional(),
NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: z.string().min(1), NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: z.string().optional(),
NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: z.string().min(1), // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: z.string().optional(),
}, },
// If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually
// runtimeEnv: { // runtimeEnv: {

View File

@ -2,6 +2,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
output: "standalone", // Erforderlich für Docker-Deployment
reactCompiler: true, reactCompiler: true,
}; };