Replace all marketing placeholder content with real MYeasyCMS content
- Logo: Replace generic Makerkit SVG with MYeasyCMS branded logo (grid icon + styled text) - Blog: Replace 3 SaaS placeholder posts with 5 real articles (Vereinsverwaltung, SEPA, Website, DSGVO, Mitglieder-Tipps) - Changelog: Replace 6 generic entries with real feature announcements (Verbandsverwaltung, Fischerei, Dateien, Kurse, Einladungen, i18n) - Documentation: Rewrite all 20 docs from Makerkit references to MYeasyCMS content - FAQ: Replace 6 generic SaaS questions with 10 real MYeasyCMS questions - Navigation: Replace Changelog link with Contact in main nav - Footer: Reorganize into Product/Company/Legal sections - Translations: Update all EN marketing strings to match real Com.BISS content
This commit is contained in:
@@ -1,25 +1,37 @@
|
||||
---
|
||||
title: "Authentication Overview"
|
||||
description: "Learn how to set up authentication in your MakerKit application."
|
||||
title: "Benutzerkonten"
|
||||
description: "Benutzerkonten in MYeasyCMS — Registrierung, Login und Profilverwaltung."
|
||||
publishedAt: 2024-04-11
|
||||
order: 1
|
||||
order: 0
|
||||
status: "published"
|
||||
---
|
||||
|
||||
MakerKit uses Supabase to manage authentication within your application.
|
||||
Jeder Benutzer in MYeasyCMS hat ein persönliches Konto mit eigenen Zugangsdaten und Berechtigungen.
|
||||
|
||||
By default, every kit comes with the following built-in authentication methods:
|
||||
- **Email/Password** - we added, by default, the traditional way of signing in
|
||||
- **Third Party Providers** - we also added by default Google Auth sign-in
|
||||
- **Email Links**
|
||||
- **Phone Number**
|
||||
## Anmeldung
|
||||
|
||||
You're free to add (or remove) any of the methods supported by Supabase's
|
||||
Authentication: we will see how.
|
||||
Melden Sie sich mit Ihrer E-Mail-Adresse und Ihrem Passwort an. Beim ersten Login werden Sie aufgefordert, Ihr Passwort zu ändern.
|
||||
|
||||
This documentation will help you with the following:
|
||||
- **Setup** - setting up your Supabase project
|
||||
- **SSR** - use SSR to persist your users' authentication, adding new
|
||||
providers
|
||||
- **Customization** - an overview of how MakerKit works so that you can adapt
|
||||
it to your own application's needs
|
||||
## Rollen
|
||||
|
||||
Jedem Benutzer wird eine Rolle zugewiesen, die seinen Zugriff steuert:
|
||||
|
||||
- **Administrator** — Vollzugriff auf alle Module und Einstellungen
|
||||
- **Kassenwart** — Finanzen, Beiträge, Mitgliederdaten
|
||||
- **Kursleiter** — Kurse, Teilnehmer
|
||||
- **Schriftführer** — Protokolle, Dokumente
|
||||
- **Mitglied** — Zugriff über das Mitgliederportal (eingeschränkt)
|
||||
|
||||
## Profil
|
||||
|
||||
Unter **Profil** können Sie Ihre persönlichen Daten aktualisieren:
|
||||
|
||||
- Name und Kontaktdaten
|
||||
- E-Mail-Adresse
|
||||
- Passwort ändern
|
||||
- Sprache wählen (Deutsch / Englisch)
|
||||
- Multi-Faktor-Authentifizierung aktivieren
|
||||
|
||||
## Multi-Faktor-Authentifizierung (MFA)
|
||||
|
||||
Für zusätzliche Sicherheit können Sie MFA aktivieren. Bei der Anmeldung wird dann neben dem Passwort ein zusätzlicher Code abgefragt — über eine Authenticator-App auf Ihrem Smartphone.
|
||||
|
||||
@@ -1,55 +1,28 @@
|
||||
---
|
||||
title: "Configuration"
|
||||
description: "Learn how authentication works in MakerKit and how to configure it."
|
||||
title: "Anmeldung konfigurieren"
|
||||
description: "Login-Optionen und Sicherheitseinstellungen in MYeasyCMS konfigurieren."
|
||||
publishedAt: 2024-04-11
|
||||
order: 1
|
||||
status: "published"
|
||||
---
|
||||
|
||||
The way you want your users to authenticate can be driven via configuration.
|
||||
MYeasyCMS bietet verschiedene Anmeldemethoden, die vom Administrator konfiguriert werden können.
|
||||
|
||||
If you open the global configuration at `src/configuration.ts`, you'll find
|
||||
the `auth` object:
|
||||
## E-Mail und Passwort
|
||||
|
||||
```tsx title="configuration.ts"
|
||||
import type { Provider } from '@supabase/gotrue-js/src/lib/types';
|
||||
Die Standard-Anmeldung erfolgt mit E-Mail-Adresse und Passwort. Passwörter müssen eine Mindestlänge von 8 Zeichen haben und sollten Groß- und Kleinbuchstaben sowie Zahlen enthalten.
|
||||
|
||||
auth: {
|
||||
requireEmailConfirmation: false,
|
||||
providers: {
|
||||
emailPassword: true,
|
||||
phoneNumber: false,
|
||||
emailLink: false,
|
||||
oAuth: ['google'] as Provider[],
|
||||
},
|
||||
}
|
||||
```
|
||||
## Passwort zurücksetzen
|
||||
|
||||
As you can see, the `providers` object can be configured to only display the
|
||||
auth methods we want to use.
|
||||
Benutzer können ihr Passwort über die "Passwort vergessen"-Funktion zurücksetzen. Ein Link zur Passworterstellung wird per E-Mail zugesandt.
|
||||
|
||||
1. For example, by setting both `phoneNumber` and `emailLink` to `true`, the
|
||||
authentication pages will display the `Email Link` authentication
|
||||
and the `Phone Number` authentication forms.
|
||||
2. Instead, by setting `emailPassword` to `false`, we will remove the
|
||||
`email/password` form from the authentication and user profile pages.
|
||||
## Multi-Faktor-Authentifizierung
|
||||
|
||||
## Requiring Email Verification
|
||||
Für zusätzliche Sicherheit kann MFA aktiviert werden. Unterstützt werden:
|
||||
|
||||
This setting needs to match what you have set up in Supabase. If you require email confirmation before your users can sign in, you will have to flip the following flag in your configuration:
|
||||
- **Authenticator-Apps** — Google Authenticator, Authy oder vergleichbare TOTP-Apps
|
||||
- **Passkeys** — Biometrische Anmeldung über Fingerabdruck oder Gesichtserkennung
|
||||
|
||||
```ts
|
||||
auth: {
|
||||
requireEmailConfirmation: false,
|
||||
}
|
||||
```
|
||||
## Sitzungsdauer
|
||||
|
||||
When the flag is set to `true`, the user will not be redirected to the onboarding flow, but will instead see a successful alert asking them to confirm their email. After confirmation, they will be able to sign in.
|
||||
|
||||
When the flag is set to `false`, the application will redirect them directly to the onboarding flow.
|
||||
|
||||
## Emails sent by Supabase
|
||||
|
||||
Supabase spins up an [InBucket](http://localhost:54324/) instance where all the emails are sent: this is where you can find emails related to password reset, sign-in links, and email verifications.
|
||||
|
||||
To access the InBucket instance, you can go to the following URL: [http://localhost:54324/](http://localhost:54324/). Save this URL, you will use it very often.
|
||||
Anmeldesitzungen laufen nach einer konfigurierbaren Zeitspanne ab. Benutzer werden automatisch abgemeldet, wenn sie längere Zeit inaktiv sind.
|
||||
|
||||
@@ -1,382 +1,29 @@
|
||||
---
|
||||
title: "Email & Password"
|
||||
description: "Traditional email and password authentication."
|
||||
title: "E-Mail und Passwort"
|
||||
description: "Anmeldung mit E-Mail-Adresse und Passwort — die Standard-Authentifizierung in MYeasyCMS."
|
||||
publishedAt: 2024-04-11
|
||||
order: 2
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
Die Anmeldung mit E-Mail und Passwort ist die Standard-Authentifizierungsmethode in MYeasyCMS.
|
||||
|
||||
Email and password authentication is the traditional way users sign up and sign in.
|
||||
## Registrierung
|
||||
|
||||
## Overview
|
||||
Neue Benutzer registrieren sich mit ihrer E-Mail-Adresse und einem selbst gewählten Passwort. Nach der Registrierung wird eine Bestätigungs-E-Mail versendet.
|
||||
|
||||
Email/password authentication provides:
|
||||
- User registration with email verification
|
||||
- Secure password storage
|
||||
- Password reset functionality
|
||||
- Session management
|
||||
## Passwortanforderungen
|
||||
|
||||
## Sign Up Flow
|
||||
- Mindestens 8 Zeichen
|
||||
- Kombination aus Buchstaben und Zahlen empfohlen
|
||||
- Das Passwort kann jederzeit in den Profileinstellungen geändert werden
|
||||
|
||||
### User Registration
|
||||
## Passwort vergessen
|
||||
|
||||
```typescript
|
||||
import { signUpAction } from '~/lib/auth/actions';
|
||||
Über die "Passwort vergessen"-Funktion auf der Login-Seite kann ein neues Passwort angefordert werden. Der Benutzer erhält einen Link per E-Mail, um ein neues Passwort festzulegen.
|
||||
|
||||
const result = await signUpAction({
|
||||
email: 'user@example.com',
|
||||
password: 'SecurePassword123!',
|
||||
});
|
||||
```
|
||||
## Sicherheitshinweise
|
||||
|
||||
### Server Action Implementation
|
||||
|
||||
```typescript
|
||||
'use server';
|
||||
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
import * as z from 'zod';
|
||||
|
||||
const SignUpSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
});
|
||||
|
||||
export const signUpAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { data: authData, error } = await client.auth.signUp({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
options: {
|
||||
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return { success: true, data: authData };
|
||||
},
|
||||
{ schema: SignUpSchema }
|
||||
);
|
||||
```
|
||||
|
||||
### Sign Up Component
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { signUpAction } from '../_lib/actions';
|
||||
|
||||
export function SignUpForm() {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm();
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const result = await signUpAction(data);
|
||||
|
||||
if (result.success) {
|
||||
toast.success('Check your email to confirm your account');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<label>Email</label>
|
||||
<input
|
||||
type="email"
|
||||
{...register('email', { required: true })}
|
||||
/>
|
||||
{errors.email && <span>Email is required</span>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Password</label>
|
||||
<input
|
||||
type="password"
|
||||
{...register('password', { required: true, minLength: 8 })}
|
||||
/>
|
||||
{errors.password && <span>Password must be 8+ characters</span>}
|
||||
</div>
|
||||
|
||||
<button type="submit">Sign Up</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Sign In Flow
|
||||
|
||||
### User Login
|
||||
|
||||
```typescript
|
||||
export const signInAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { error } = await client.auth.signInWithPassword({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
redirect('/home');
|
||||
},
|
||||
{ schema: SignInSchema }
|
||||
);
|
||||
```
|
||||
|
||||
### Sign In Component
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
export function SignInForm() {
|
||||
const { register, handleSubmit } = useForm();
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
await signInAction(data);
|
||||
} catch (error) {
|
||||
toast.error('Invalid email or password');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
type="email"
|
||||
{...register('email')}
|
||||
placeholder="Email"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
{...register('password')}
|
||||
placeholder="Password"
|
||||
/>
|
||||
<button type="submit">Sign In</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Email Verification
|
||||
|
||||
### Requiring Email Confirmation
|
||||
|
||||
Configure in Supabase dashboard or config:
|
||||
|
||||
```typescript
|
||||
// config/auth.config.ts
|
||||
export const authConfig = {
|
||||
requireEmailConfirmation: true,
|
||||
};
|
||||
```
|
||||
|
||||
### Handling Unconfirmed Emails
|
||||
|
||||
```typescript
|
||||
export const signInAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { data: authData, error } = await client.auth.signInWithPassword({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
if (error.message.includes('Email not confirmed')) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Please confirm your email before signing in',
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
redirect('/home');
|
||||
},
|
||||
{ schema: SignInSchema }
|
||||
);
|
||||
```
|
||||
|
||||
## Password Reset
|
||||
|
||||
### Request Password Reset
|
||||
|
||||
```typescript
|
||||
export const requestPasswordResetAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { error } = await client.auth.resetPasswordForEmail(data.email, {
|
||||
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Check your email for reset instructions',
|
||||
};
|
||||
},
|
||||
{
|
||||
schema: z.object({
|
||||
email: z.string().email(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Reset Password Form
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
export function PasswordResetRequestForm() {
|
||||
const { register, handleSubmit } = useForm();
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const result = await requestPasswordResetAction(data);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
type="email"
|
||||
{...register('email')}
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
<button type="submit">Send Reset Link</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Update Password
|
||||
|
||||
```typescript
|
||||
export const updatePasswordAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { error } = await client.auth.updateUser({
|
||||
password: data.newPassword,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
redirect('/home');
|
||||
},
|
||||
{
|
||||
schema: z.object({
|
||||
newPassword: z.string().min(8),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Password Requirements
|
||||
|
||||
### Validation Schema
|
||||
|
||||
```typescript
|
||||
const PasswordSchema = z
|
||||
.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain an uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain a lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain a number')
|
||||
.regex(/[^A-Za-z0-9]/, 'Password must contain a special character');
|
||||
```
|
||||
|
||||
### Password Strength Indicator
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export function PasswordInput() {
|
||||
const [password, setPassword] = useState('');
|
||||
const strength = calculatePasswordStrength(password);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<div className="flex gap-1">
|
||||
{[1, 2, 3, 4].map((level) => (
|
||||
<div
|
||||
key={level}
|
||||
className={cn(
|
||||
'h-1 flex-1 rounded',
|
||||
strength >= level ? 'bg-green-500' : 'bg-gray-200'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-sm">
|
||||
{strength === 4 && 'Strong password'}
|
||||
{strength === 3 && 'Good password'}
|
||||
{strength === 2 && 'Fair password'}
|
||||
{strength === 1 && 'Weak password'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Session Management
|
||||
|
||||
### Checking Authentication Status
|
||||
|
||||
```typescript
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
export async function requireAuth() {
|
||||
const client = getSupabaseServerClient();
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
redirect('/auth/sign-in');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
### Sign Out
|
||||
|
||||
```typescript
|
||||
export const signOutAction = enhanceAction(
|
||||
async () => {
|
||||
const client = getSupabaseServerClient();
|
||||
await client.auth.signOut();
|
||||
redirect('/auth/sign-in');
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Enforce strong passwords** - Minimum 8 characters, mixed case, numbers, symbols
|
||||
2. **Rate limit login attempts** - Prevent brute force attacks
|
||||
3. **Use HTTPS only** - Encrypt data in transit
|
||||
4. **Enable email verification** - Confirm email ownership
|
||||
5. **Implement account lockout** - After failed attempts
|
||||
6. **Log authentication events** - Track sign-ins and failures
|
||||
7. **Support 2FA** - Add extra security layer
|
||||
- Verwenden Sie ein einzigartiges Passwort für MYeasyCMS
|
||||
- Geben Sie Ihre Zugangsdaten nicht an andere weiter
|
||||
- Aktivieren Sie die Multi-Faktor-Authentifizierung für zusätzliche Sicherheit
|
||||
|
||||
@@ -1,392 +1,30 @@
|
||||
---
|
||||
title: "Magic Links"
|
||||
description: "Passwordless authentication with email magic links."
|
||||
title: "Einladungslinks"
|
||||
description: "Benutzer per E-Mail-Einladung in den Vereins-Account einladen."
|
||||
publishedAt: 2024-04-11
|
||||
order: 4
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
Administratoren können neue Benutzer per Einladungslink in den Vereins-Account einladen.
|
||||
|
||||
Magic links provide passwordless authentication by sending a one-time link to the user's email.
|
||||
## So funktioniert die Einladung
|
||||
|
||||
## How It Works
|
||||
1. Navigieren Sie zu **Einstellungen → Mitarbeiter → Einladen**
|
||||
2. Geben Sie die E-Mail-Adresse des neuen Benutzers ein
|
||||
3. Wählen Sie die Rolle (Administrator, Kassenwart, Kursleiter, etc.)
|
||||
4. Klicken Sie auf **Einladung senden**
|
||||
|
||||
1. User enters their email address
|
||||
2. System sends an email with a unique link
|
||||
3. User clicks the link in their email
|
||||
4. User is automatically signed in
|
||||
Der eingeladene Benutzer erhält eine E-Mail mit einem Registrierungslink. Nach der Registrierung hat er sofort Zugriff auf die Funktionen, die seiner Rolle entsprechen.
|
||||
|
||||
## Benefits
|
||||
## Einladungen verwalten
|
||||
|
||||
- **No password to remember** - Better UX
|
||||
- **More secure** - No password to steal
|
||||
- **Lower friction** - Faster sign-up process
|
||||
- **Email verification** - Confirms email ownership
|
||||
Unter **Mitglieder → Einladungen** sehen Sie alle offenen und angenommenen Einladungen:
|
||||
|
||||
## Implementation
|
||||
- **Offen** — Einladung wurde versendet, aber noch nicht angenommen
|
||||
- **Angenommen** — Benutzer hat die Einladung angenommen und sein Konto erstellt
|
||||
- **Abgelaufen** — Einladungslink ist nicht mehr gültig (kann erneut gesendet werden)
|
||||
|
||||
### Magic Link Form
|
||||
## Erneut einladen
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { sendMagicLinkAction } from '../_lib/actions';
|
||||
|
||||
export function MagicLinkForm() {
|
||||
const { register, handleSubmit, formState: { isSubmitting } } = useForm();
|
||||
const [sent, setSent] = useState(false);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const result = await sendMagicLinkAction(data);
|
||||
|
||||
if (result.success) {
|
||||
setSent(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (sent) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<h2>Check your email</h2>
|
||||
<p>We've sent you a magic link to sign in.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<label>Email address</label>
|
||||
<input
|
||||
type="email"
|
||||
{...register('email', { required: true })}
|
||||
placeholder="you@example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Sending...' : 'Send magic link'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Server Action
|
||||
|
||||
```typescript
|
||||
'use server';
|
||||
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import * as z from 'zod';
|
||||
|
||||
export const sendMagicLinkAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
||||
|
||||
const { error } = await client.auth.signInWithOtp({
|
||||
email: data.email,
|
||||
options: {
|
||||
emailRedirectTo: `${origin}/auth/callback`,
|
||||
shouldCreateUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Check your email for the magic link',
|
||||
};
|
||||
},
|
||||
{
|
||||
schema: z.object({
|
||||
email: z.string().email(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Enable in Supabase
|
||||
|
||||
1. Go to **Authentication** → **Providers** → **Email**
|
||||
2. Enable "Enable Email Provider"
|
||||
3. Enable "Enable Email Confirmations"
|
||||
|
||||
### Configure Email Template
|
||||
|
||||
Customize the magic link email in Supabase Dashboard:
|
||||
|
||||
1. Go to **Authentication** → **Email Templates**
|
||||
2. Select "Magic Link"
|
||||
3. Customize the template:
|
||||
|
||||
```html
|
||||
<h2>Sign in to {{ .SiteURL }}</h2>
|
||||
<p>Click the link below to sign in:</p>
|
||||
<p><a href="{{ .ConfirmationURL }}">Sign in</a></p>
|
||||
<p>This link expires in {{ .TokenExpiryHours }} hours.</p>
|
||||
```
|
||||
|
||||
## Callback Handler
|
||||
|
||||
Handle the magic link callback:
|
||||
|
||||
```typescript
|
||||
// app/auth/callback/route.ts
|
||||
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { cookies } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const requestUrl = new URL(request.url);
|
||||
const token_hash = requestUrl.searchParams.get('token_hash');
|
||||
const type = requestUrl.searchParams.get('type');
|
||||
|
||||
if (token_hash && type === 'magiclink') {
|
||||
const cookieStore = cookies();
|
||||
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
token_hash,
|
||||
type: 'magiclink',
|
||||
});
|
||||
|
||||
if (!error) {
|
||||
return NextResponse.redirect(new URL('/home', request.url));
|
||||
}
|
||||
}
|
||||
|
||||
// Return error if verification failed
|
||||
return NextResponse.redirect(
|
||||
new URL('/auth/sign-in?error=invalid_link', request.url)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Redirect
|
||||
|
||||
Specify where users go after clicking the link:
|
||||
|
||||
```typescript
|
||||
await client.auth.signInWithOtp({
|
||||
email: data.email,
|
||||
options: {
|
||||
emailRedirectTo: `${origin}/onboarding`,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Disable Auto Sign-Up
|
||||
|
||||
Require users to sign up first:
|
||||
|
||||
```typescript
|
||||
await client.auth.signInWithOtp({
|
||||
email: data.email,
|
||||
options: {
|
||||
shouldCreateUser: false, // Don't create new users
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Token Expiry
|
||||
|
||||
Configure link expiration (default: 1 hour):
|
||||
|
||||
```sql
|
||||
-- In Supabase SQL Editor
|
||||
ALTER TABLE auth.users
|
||||
SET default_token_lifetime = '15 minutes';
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Prevent abuse by rate limiting magic link requests:
|
||||
|
||||
```typescript
|
||||
import { ratelimit } from '~/lib/rate-limit';
|
||||
|
||||
export const sendMagicLinkAction = enhanceAction(
|
||||
async (data, user, request) => {
|
||||
// Rate limit by IP
|
||||
const ip = request.headers.get('x-forwarded-for') || 'unknown';
|
||||
const { success } = await ratelimit.limit(ip);
|
||||
|
||||
if (!success) {
|
||||
throw new Error('Too many requests. Please try again later.');
|
||||
}
|
||||
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
await client.auth.signInWithOtp({
|
||||
email: data.email,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
{ schema: EmailSchema }
|
||||
);
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Link Expiration
|
||||
|
||||
Magic links should expire quickly:
|
||||
- Default: 1 hour
|
||||
- Recommended: 15-30 minutes for production
|
||||
- Shorter for sensitive actions
|
||||
|
||||
### One-Time Use
|
||||
|
||||
Links should be invalidated after use:
|
||||
|
||||
```typescript
|
||||
// Supabase handles this automatically
|
||||
// Each link can only be used once
|
||||
```
|
||||
|
||||
### Email Verification
|
||||
|
||||
Ensure emails are verified:
|
||||
|
||||
```typescript
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
|
||||
if (!user.email_confirmed_at) {
|
||||
redirect('/verify-email');
|
||||
}
|
||||
```
|
||||
|
||||
## User Experience
|
||||
|
||||
### Loading State
|
||||
|
||||
Show feedback while sending:
|
||||
|
||||
```tsx
|
||||
export function MagicLinkForm() {
|
||||
const [status, setStatus] = useState<'idle' | 'sending' | 'sent'>('idle');
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
setStatus('sending');
|
||||
await sendMagicLinkAction(data);
|
||||
setStatus('sent');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{status === 'idle' && <EmailForm onSubmit={onSubmit} />}
|
||||
{status === 'sending' && <SendingMessage />}
|
||||
{status === 'sent' && <CheckEmailMessage />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Resend Link
|
||||
|
||||
Allow users to request a new link:
|
||||
|
||||
```tsx
|
||||
export function ResendMagicLink({ email }: { email: string }) {
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [countdown, setCountdown] = useState(60);
|
||||
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
setCanResend(true);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
const handleResend = async () => {
|
||||
await sendMagicLinkAction({ email });
|
||||
setCountdown(60);
|
||||
setCanResend(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={handleResend} disabled={!canResend}>
|
||||
{canResend ? 'Resend link' : `Resend in ${countdown}s`}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Email Deliverability
|
||||
|
||||
### SPF, DKIM, DMARC
|
||||
|
||||
Configure email authentication:
|
||||
1. Add SPF record to DNS
|
||||
2. Enable DKIM signing
|
||||
3. Set up DMARC policy
|
||||
|
||||
### Custom Email Domain
|
||||
|
||||
Use your own domain for better deliverability:
|
||||
|
||||
1. Go to **Project Settings** → **Auth**
|
||||
2. Configure custom SMTP
|
||||
3. Verify domain ownership
|
||||
|
||||
### Monitor Bounces
|
||||
|
||||
Track email delivery issues:
|
||||
|
||||
```typescript
|
||||
// Handle email bounces
|
||||
export async function handleEmailBounce(email: string) {
|
||||
await client.from('email_bounces').insert({
|
||||
email,
|
||||
bounced_at: new Date(),
|
||||
});
|
||||
|
||||
// Notify user via other channel
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Local Development
|
||||
|
||||
In development, emails go to InBucket:
|
||||
|
||||
```
|
||||
http://localhost:54324
|
||||
```
|
||||
|
||||
Check this URL to see magic link emails during testing.
|
||||
|
||||
### Test Mode
|
||||
|
||||
Create a test link without sending email:
|
||||
|
||||
```typescript
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Magic link URL:', confirmationUrl);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Clear communication** - Tell users to check spam
|
||||
2. **Short expiry** - 15-30 minutes for security
|
||||
3. **Rate limiting** - Prevent abuse
|
||||
4. **Fallback option** - Offer password auth as backup
|
||||
5. **Custom domain** - Better deliverability
|
||||
6. **Monitor delivery** - Track bounces and failures
|
||||
7. **Resend option** - Let users request new link
|
||||
8. **Mobile-friendly** - Ensure links work on mobile
|
||||
Abgelaufene oder nicht angenommene Einladungen können mit einem Klick erneut versendet werden.
|
||||
|
||||
@@ -1,395 +1,31 @@
|
||||
---
|
||||
title: "OAuth"
|
||||
description: "Sign in with Google, GitHub, and other OAuth providers."
|
||||
title: "Mitgliederportal"
|
||||
description: "Das Mitgliederportal ermöglicht Vereinsmitgliedern den Zugriff auf ihre eigenen Daten."
|
||||
publishedAt: 2024-04-11
|
||||
order: 3
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
Das Mitgliederportal ist der Bereich von MYeasyCMS, der für die Vereinsmitglieder selbst zugänglich ist.
|
||||
|
||||
Allow users to sign in with their existing accounts from Google, GitHub, and other providers.
|
||||
## Funktionen für Mitglieder
|
||||
|
||||
## Supported Providers
|
||||
Über das Portal können Mitglieder:
|
||||
|
||||
Supabase supports many OAuth providers:
|
||||
- Google
|
||||
- GitHub
|
||||
- GitLab
|
||||
- Bitbucket
|
||||
- Azure
|
||||
- Facebook
|
||||
- Twitter
|
||||
- Discord
|
||||
- Slack
|
||||
- And more...
|
||||
- **Stammdaten einsehen und aktualisieren** — Adresse, Telefonnummer, E-Mail
|
||||
- **Kursanmeldungen** — Sich für Kurse und Veranstaltungen anmelden
|
||||
- **Beitragshistorie** — Ihre Zahlungshistorie einsehen
|
||||
- **Dokumente** — Persönliche Dokumente herunterladen (Ausweis, Bestätigungen)
|
||||
- **Newsletter** — Newsletter-Abonnement verwalten
|
||||
|
||||
## Setting Up OAuth
|
||||
## Zugang einrichten
|
||||
|
||||
### Configure in Supabase Dashboard
|
||||
Mitglieder erhalten Zugang zum Portal über die Vereinswebsite. Der Login erfolgt mit E-Mail-Adresse und Passwort.
|
||||
|
||||
1. Go to **Authentication** → **Providers**
|
||||
2. Enable your desired provider (e.g., Google)
|
||||
3. Add your OAuth credentials:
|
||||
- **Client ID**
|
||||
- **Client Secret**
|
||||
- **Redirect URL**: `https://your-project.supabase.co/auth/v1/callback`
|
||||
## Datenschutz
|
||||
|
||||
### Google OAuth Setup
|
||||
Jedes Mitglied sieht ausschließlich seine eigenen Daten. Ein Zugriff auf andere Mitgliederdaten oder Verwaltungsfunktionen ist nicht möglich.
|
||||
|
||||
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
||||
2. Create a new project or select existing
|
||||
3. Enable Google+ API
|
||||
4. Create OAuth 2.0 credentials
|
||||
5. Add authorized redirect URIs:
|
||||
- Production: `https://your-project.supabase.co/auth/v1/callback`
|
||||
- Development: `http://localhost:54321/auth/v1/callback`
|
||||
## Kontoverknüpfung
|
||||
|
||||
### GitHub OAuth Setup
|
||||
|
||||
1. Go to GitHub Settings → Developer Settings → OAuth Apps
|
||||
2. Click "New OAuth App"
|
||||
3. Fill in details:
|
||||
- **Application name**: Your App
|
||||
- **Homepage URL**: `https://yourapp.com`
|
||||
- **Authorization callback URL**: `https://your-project.supabase.co/auth/v1/callback`
|
||||
4. Copy Client ID and Client Secret to Supabase
|
||||
|
||||
## Implementation
|
||||
|
||||
### OAuth Sign In Button
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { signInWithOAuthAction } from '../_lib/actions';
|
||||
|
||||
export function OAuthButtons() {
|
||||
const handleGoogleSignIn = async () => {
|
||||
await signInWithOAuthAction('google');
|
||||
};
|
||||
|
||||
const handleGitHubSignIn = async () => {
|
||||
await signInWithOAuthAction('github');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<button
|
||||
onClick={handleGoogleSignIn}
|
||||
className="w-full flex items-center justify-center gap-2 border rounded-lg p-2"
|
||||
>
|
||||
<GoogleIcon />
|
||||
Continue with Google
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleGitHubSignIn}
|
||||
className="w-full flex items-center justify-center gap-2 border rounded-lg p-2"
|
||||
>
|
||||
<GitHubIcon />
|
||||
Continue with GitHub
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Server Action
|
||||
|
||||
```typescript
|
||||
'use server';
|
||||
|
||||
import { enhanceAction } from '@kit/next/actions';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import * as z from 'zod';
|
||||
|
||||
const OAuthProviderSchema = z.enum([
|
||||
'google',
|
||||
'github',
|
||||
'gitlab',
|
||||
'azure',
|
||||
'facebook',
|
||||
]);
|
||||
|
||||
export const signInWithOAuthAction = enhanceAction(
|
||||
async (provider) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
||||
|
||||
const { data, error } = await client.auth.signInWithOAuth({
|
||||
provider,
|
||||
options: {
|
||||
redirectTo: `${origin}/auth/callback`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Redirect to OAuth provider
|
||||
redirect(data.url);
|
||||
},
|
||||
{
|
||||
schema: OAuthProviderSchema,
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### OAuth Callback Handler
|
||||
|
||||
```typescript
|
||||
// app/auth/callback/route.ts
|
||||
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { cookies } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
|
||||
if (code) {
|
||||
const cookieStore = cookies();
|
||||
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
||||
|
||||
await supabase.auth.exchangeCodeForSession(code);
|
||||
}
|
||||
|
||||
// Redirect to home page
|
||||
return NextResponse.redirect(new URL('/home', request.url));
|
||||
}
|
||||
```
|
||||
|
||||
## Customizing OAuth Flow
|
||||
|
||||
### Scopes
|
||||
|
||||
Request specific permissions:
|
||||
|
||||
```typescript
|
||||
await client.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
scopes: 'email profile https://www.googleapis.com/auth/calendar',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Pass custom parameters:
|
||||
|
||||
```typescript
|
||||
await client.auth.signInWithOAuth({
|
||||
provider: 'azure',
|
||||
options: {
|
||||
queryParams: {
|
||||
prompt: 'consent',
|
||||
access_type: 'offline',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Skip Browser Redirect
|
||||
|
||||
For mobile apps or custom flows:
|
||||
|
||||
```typescript
|
||||
const { data } = await client.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
skipBrowserRedirect: true,
|
||||
},
|
||||
});
|
||||
|
||||
// data.url contains the OAuth URL
|
||||
// Handle redirect manually
|
||||
```
|
||||
|
||||
## Account Linking
|
||||
|
||||
### Linking Additional Providers
|
||||
|
||||
Allow users to link multiple OAuth accounts:
|
||||
|
||||
```typescript
|
||||
export const linkOAuthProviderAction = enhanceAction(
|
||||
async (provider) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const user = await requireAuth();
|
||||
|
||||
const { data, error } = await client.auth.linkIdentity({
|
||||
provider,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
redirect(data.url);
|
||||
},
|
||||
{ schema: OAuthProviderSchema, auth: true }
|
||||
);
|
||||
```
|
||||
|
||||
### Unlinking Providers
|
||||
|
||||
```typescript
|
||||
export const unlinkOAuthProviderAction = enhanceAction(
|
||||
async ({ provider, identityId }) => {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { error } = await client.auth.unlinkIdentity({
|
||||
identity_id: identityId,
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
revalidatePath('/settings/security');
|
||||
},
|
||||
{
|
||||
schema: z.object({
|
||||
provider: z.string(),
|
||||
identityId: z.string(),
|
||||
}),
|
||||
auth: true,
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Viewing Linked Identities
|
||||
|
||||
```typescript
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
export async function getLinkedIdentities() {
|
||||
const client = getSupabaseServerClient();
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
|
||||
return user?.identities || [];
|
||||
}
|
||||
```
|
||||
|
||||
## User Data from OAuth
|
||||
|
||||
### Accessing Provider Data
|
||||
|
||||
```typescript
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
|
||||
// User metadata from provider
|
||||
const {
|
||||
full_name,
|
||||
avatar_url,
|
||||
email,
|
||||
} = user.user_metadata;
|
||||
|
||||
// Provider-specific data
|
||||
const identities = user.identities || [];
|
||||
const googleIdentity = identities.find(i => i.provider === 'google');
|
||||
|
||||
console.log(googleIdentity?.identity_data);
|
||||
```
|
||||
|
||||
### Storing Additional Data
|
||||
|
||||
```typescript
|
||||
export const completeOAuthProfileAction = enhanceAction(
|
||||
async (data) => {
|
||||
const client = getSupabaseServerClient();
|
||||
const user = await requireAuth();
|
||||
|
||||
// Update user metadata
|
||||
await client.auth.updateUser({
|
||||
data: {
|
||||
username: data.username,
|
||||
bio: data.bio,
|
||||
},
|
||||
});
|
||||
|
||||
// Update profile in database
|
||||
await client.from('profiles').upsert({
|
||||
id: user.id,
|
||||
username: data.username,
|
||||
bio: data.bio,
|
||||
avatar_url: user.user_metadata.avatar_url,
|
||||
});
|
||||
|
||||
redirect('/home');
|
||||
},
|
||||
{ schema: ProfileSchema, auth: true }
|
||||
);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Enable OAuth in Config
|
||||
|
||||
```typescript
|
||||
// config/auth.config.ts
|
||||
export const authConfig = {
|
||||
providers: {
|
||||
emailPassword: true,
|
||||
oAuth: ['google', 'github'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```tsx
|
||||
import { authConfig } from '~/config/auth.config';
|
||||
|
||||
export function AuthProviders() {
|
||||
return (
|
||||
<>
|
||||
{authConfig.providers.emailPassword && <EmailPasswordForm />}
|
||||
|
||||
{authConfig.providers.oAuth?.includes('google') && (
|
||||
<GoogleSignInButton />
|
||||
)}
|
||||
|
||||
{authConfig.providers.oAuth?.includes('github') && (
|
||||
<GitHubSignInButton />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Redirect URI Mismatch
|
||||
|
||||
Ensure redirect URIs match exactly:
|
||||
- Check Supabase Dashboard → Authentication → URL Configuration
|
||||
- Verify OAuth app settings in provider console
|
||||
- Use exact URLs (including http/https)
|
||||
|
||||
### Missing Email
|
||||
|
||||
Some providers don't share email by default:
|
||||
|
||||
```typescript
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
|
||||
if (!user.email) {
|
||||
// Request email separately or prompt user
|
||||
redirect('/auth/complete-profile');
|
||||
}
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
OAuth providers may rate limit requests:
|
||||
- Cache OAuth tokens appropriately
|
||||
- Don't make excessive authorization requests
|
||||
- Handle rate limit errors gracefully
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Request minimum scopes** - Only ask for what you need
|
||||
2. **Handle errors gracefully** - OAuth can fail for many reasons
|
||||
3. **Verify email addresses** - Some providers don't verify emails
|
||||
4. **Support account linking** - Let users connect multiple providers
|
||||
5. **Provide fallback** - Always offer email/password as backup
|
||||
6. **Log OAuth events** - Track sign-ins and linking attempts
|
||||
7. **Test thoroughly** - Test with real provider accounts
|
||||
Mitglieder, die in mehreren Vereinen aktiv sind, können ihre Konten verknüpfen und zwischen den Vereinen wechseln, ohne sich erneut anmelden zu müssen.
|
||||
|
||||
Reference in New Issue
Block a user