Changelog (#399)

* feat: add changelog feature and update site navigation

- Introduced a new Changelog page with pagination and detailed entry views.
- Added components for displaying changelog entries, pagination, and entry details.
- Updated site navigation to include a link to the new Changelog page.
- Enhanced localization for changelog-related texts in marketing.json.

* refactor: enhance Changelog page layout and entry display

- Increased the number of changelog entries displayed per page from 2 to 20 for better visibility.
- Improved the layout of the Changelog page by adjusting the container styles and removing unnecessary divs.
- Updated the ChangelogEntry component to enhance the visual presentation of entries, including a new date badge with an icon.
- Refined the CSS styles for Markdoc headings to improve typography and spacing.

* refactor: enhance Changelog page functionality and layout

- Increased the number of changelog entries displayed per page from 20 to 50 for improved user experience.
- Updated ChangelogEntry component to make the highlight prop optional and refined the layout for better visual clarity.
- Adjusted styles in ChangelogHeader and ChangelogPagination components for a more cohesive design.
- Removed unnecessary order fields from changelog markdown files to streamline content management.

* feat: enhance Changelog entry navigation and data loading

- Refactored ChangelogEntry page to load previous and next entries for improved navigation.
- Introduced ChangelogNavigation component to facilitate navigation between changelog entries.
- Updated ChangelogDetail component to display navigation links and entry details.
- Enhanced data fetching logic to retrieve all changelog entries alongside the current entry.
- Added localization keys for navigation text in marketing.json.

* Update package dependencies and enhance documentation layout

- Upgraded various packages including @turbo/gen and turbo to version 2.6.0, and react-hook-form to version 7.66.0.
- Updated lucide-react to version 0.552.0 across multiple packages.
- Refactored documentation layout components for improved styling and structure.
- Removed deprecated loading components and adjusted navigation elements for better user experience.
- Added placeholder notes in changelog entries for clarity.
This commit is contained in:
Giancarlo Buomprisco
2025-11-01 11:59:52 +07:00
committed by GitHub
parent a920dea2b3
commit 116d41a284
73 changed files with 5638 additions and 558 deletions

View File

@@ -1,5 +1,5 @@
---
title: "Authentication"
title: "Authentication Overview"
description: "Learn how to set up authentication in your MakerKit application."
publishedAt: 2024-04-11
order: 1

View File

@@ -0,0 +1,382 @@
---
title: "Email & Password"
description: "Traditional email and password authentication."
publishedAt: 2024-04-11
order: 2
status: "published"
---
> **Note:** This is mock/placeholder content for demonstration purposes.
Email and password authentication is the traditional way users sign up and sign in.
## Overview
Email/password authentication provides:
- User registration with email verification
- Secure password storage
- Password reset functionality
- Session management
## Sign Up Flow
### User Registration
```typescript
import { signUpAction } from '~/lib/auth/actions';
const result = await signUpAction({
email: 'user@example.com',
password: 'SecurePassword123!',
});
```
### Server Action Implementation
```typescript
'use server';
import { enhanceAction } from '@kit/next/actions';
import { 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

View File

@@ -0,0 +1,392 @@
---
title: "Magic Links"
description: "Passwordless authentication with email magic links."
publishedAt: 2024-04-11
order: 4
status: "published"
---
> **Note:** This is mock/placeholder content for demonstration purposes.
Magic links provide passwordless authentication by sending a one-time link to the user's email.
## How It Works
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
## Benefits
- **No password to remember** - Better UX
- **More secure** - No password to steal
- **Lower friction** - Faster sign-up process
- **Email verification** - Confirms email ownership
## Implementation
### Magic Link Form
```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 { 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

View File

@@ -0,0 +1,395 @@
---
title: "OAuth"
description: "Sign in with Google, GitHub, and other OAuth providers."
publishedAt: 2024-04-11
order: 3
status: "published"
---
> **Note:** This is mock/placeholder content for demonstration purposes.
Allow users to sign in with their existing accounts from Google, GitHub, and other providers.
## Supported Providers
Supabase supports many OAuth providers:
- Google
- GitHub
- GitLab
- Bitbucket
- Azure
- Facebook
- Twitter
- Discord
- Slack
- And more...
## Setting Up OAuth
### Configure in Supabase Dashboard
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`
### Google OAuth Setup
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`
### 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 { 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