Initial commit
This commit is contained in:
421
commands/auth-setup.md
Normal file
421
commands/auth-setup.md
Normal file
@@ -0,0 +1,421 @@
|
||||
---
|
||||
description: Generate authentication boilerplate with JWT, OAuth, and session support
|
||||
shortcut: as
|
||||
category: backend
|
||||
difficulty: intermediate
|
||||
estimated_time: 5-10 minutes
|
||||
---
|
||||
|
||||
# Auth Setup
|
||||
|
||||
Generates complete authentication boilerplate including JWT, OAuth (Google/GitHub), session management, and password reset flows.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Auth System:**
|
||||
- JWT authentication with refresh tokens
|
||||
- OAuth2 (Google, GitHub, Facebook)
|
||||
- Password hashing (bcrypt)
|
||||
- Email verification
|
||||
- Password reset flow
|
||||
- Session management
|
||||
- Rate limiting on auth endpoints
|
||||
- Authentication middleware
|
||||
|
||||
**Output:** Complete authentication system ready for production
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate full auth system
|
||||
/auth-setup jwt
|
||||
|
||||
# Shortcut
|
||||
/as oauth --providers google,github
|
||||
|
||||
# With specific features
|
||||
/as jwt --features email-verification,password-reset,2fa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
### **JWT Authentication**
|
||||
|
||||
**auth.service.ts:**
|
||||
```typescript
|
||||
import bcrypt from 'bcrypt'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { User } from './models/User'
|
||||
|
||||
export class AuthService {
|
||||
async register(email: string, password: string, name: string) {
|
||||
// Check if user exists
|
||||
const existing = await User.findOne({ email })
|
||||
if (existing) {
|
||||
throw new Error('Email already registered')
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 12)
|
||||
|
||||
// Create user
|
||||
const user = await User.create({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
emailVerified: false
|
||||
})
|
||||
|
||||
// Generate verification token
|
||||
const verificationToken = this.generateToken({ userId: user.id, type: 'verify' }, '24h')
|
||||
|
||||
// Send verification email (implement sendEmail)
|
||||
await this.sendVerificationEmail(email, verificationToken)
|
||||
|
||||
// Generate auth tokens
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
const refreshToken = this.generateRefreshToken(user)
|
||||
|
||||
return {
|
||||
user: { id: user.id, email: user.email, name: user.name },
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
const user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials')
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, user.password)
|
||||
if (!validPassword) {
|
||||
throw new Error('Invalid credentials')
|
||||
}
|
||||
|
||||
if (!user.emailVerified) {
|
||||
throw new Error('Please verify your email')
|
||||
}
|
||||
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
const refreshToken = this.generateRefreshToken(user)
|
||||
|
||||
return {
|
||||
user: { id: user.id, email: user.email, name: user.name },
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string) {
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as any
|
||||
|
||||
const user = await User.findById(decoded.userId)
|
||||
if (!user) {
|
||||
throw new Error('User not found')
|
||||
}
|
||||
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
return { accessToken }
|
||||
} catch (error) {
|
||||
throw new Error('Invalid refresh token')
|
||||
}
|
||||
}
|
||||
|
||||
async verifyEmail(token: string) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
if (decoded.type !== 'verify') {
|
||||
throw new Error('Invalid token type')
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(decoded.userId, { emailVerified: true })
|
||||
return { message: 'Email verified successfully' }
|
||||
}
|
||||
|
||||
async requestPasswordReset(email: string) {
|
||||
const user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
// Don't reveal if user exists
|
||||
return { message: 'If email exists, reset link sent' }
|
||||
}
|
||||
|
||||
const resetToken = this.generateToken({ userId: user.id, type: 'reset' }, '1h')
|
||||
await this.sendPasswordResetEmail(email, resetToken)
|
||||
|
||||
return { message: 'If email exists, reset link sent' }
|
||||
}
|
||||
|
||||
async resetPassword(token: string, newPassword: string) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
if (decoded.type !== 'reset') {
|
||||
throw new Error('Invalid token type')
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 12)
|
||||
await User.findByIdAndUpdate(decoded.userId, { password: hashedPassword })
|
||||
|
||||
return { message: 'Password reset successfully' }
|
||||
}
|
||||
|
||||
private generateAccessToken(user: any) {
|
||||
return jwt.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
process.env.JWT_SECRET!,
|
||||
{ expiresIn: '15m' }
|
||||
)
|
||||
}
|
||||
|
||||
private generateRefreshToken(user: any) {
|
||||
return jwt.sign(
|
||||
{ userId: user.id },
|
||||
process.env.JWT_REFRESH_SECRET!,
|
||||
{ expiresIn: '7d' }
|
||||
)
|
||||
}
|
||||
|
||||
private generateToken(payload: any, expiresIn: string) {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn })
|
||||
}
|
||||
|
||||
private async sendVerificationEmail(email: string, token: string) {
|
||||
// Implement with SendGrid, Resend, etc.
|
||||
}
|
||||
|
||||
private async sendPasswordResetEmail(email: string, token: string) {
|
||||
// Implement with SendGrid, Resend, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **OAuth2 Setup (Google)**
|
||||
|
||||
**oauth.controller.ts:**
|
||||
```typescript
|
||||
import { OAuth2Client } from 'google-auth-library'
|
||||
|
||||
const googleClient = new OAuth2Client(
|
||||
process.env.GOOGLE_CLIENT_ID,
|
||||
process.env.GOOGLE_CLIENT_SECRET,
|
||||
process.env.GOOGLE_REDIRECT_URI
|
||||
)
|
||||
|
||||
export class OAuthController {
|
||||
async googleLogin(req: Request, res: Response) {
|
||||
const authUrl = googleClient.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: ['profile', 'email']
|
||||
})
|
||||
|
||||
res.redirect(authUrl)
|
||||
}
|
||||
|
||||
async googleCallback(req: Request, res: Response) {
|
||||
const { code } = req.query
|
||||
|
||||
const { tokens } = await googleClient.getToken(code as string)
|
||||
googleClient.setCredentials(tokens)
|
||||
|
||||
const ticket = await googleClient.verifyIdToken({
|
||||
idToken: tokens.id_token!,
|
||||
audience: process.env.GOOGLE_CLIENT_ID
|
||||
})
|
||||
|
||||
const payload = ticket.getPayload()
|
||||
if (!payload) {
|
||||
throw new Error('Invalid token')
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
let user = await User.findOne({ email: payload.email })
|
||||
|
||||
if (!user) {
|
||||
user = await User.create({
|
||||
email: payload.email,
|
||||
name: payload.name,
|
||||
avatar: payload.picture,
|
||||
emailVerified: true,
|
||||
provider: 'google',
|
||||
providerId: payload.sub
|
||||
})
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = generateAccessToken(user)
|
||||
const refreshToken = generateRefreshToken(user)
|
||||
|
||||
// Redirect with tokens
|
||||
res.redirect(`/auth/success?token=${accessToken}&refresh=${refreshToken}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Authentication Middleware**
|
||||
|
||||
**auth.middleware.ts:**
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: {
|
||||
userId: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function authenticate(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' })
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1]
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
req.user = {
|
||||
userId: decoded.userId,
|
||||
email: decoded.email
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
return res.status(401).json({ error: 'Token expired' })
|
||||
}
|
||||
return res.status(401).json({ error: 'Invalid token' })
|
||||
}
|
||||
}
|
||||
|
||||
export function authorize(...roles: string[]) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' })
|
||||
}
|
||||
|
||||
const user = await User.findById(req.user.userId)
|
||||
|
||||
if (!user || !roles.includes(user.role)) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' })
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Rate Limiting**
|
||||
|
||||
```typescript
|
||||
import rateLimit from 'express-rate-limit'
|
||||
|
||||
export const authLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 5, // 5 requests per window
|
||||
message: 'Too many login attempts, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false
|
||||
})
|
||||
|
||||
// Usage
|
||||
app.post('/api/auth/login', authLimiter, authController.login)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# JWT
|
||||
JWT_SECRET=your-super-secret-key-min-32-chars
|
||||
JWT_REFRESH_SECRET=your-refresh-secret-key
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# OAuth - Google
|
||||
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret
|
||||
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/google/callback
|
||||
|
||||
# OAuth - GitHub
|
||||
GITHUB_CLIENT_ID=your-github-client-id
|
||||
GITHUB_CLIENT_SECRET=your-github-client-secret
|
||||
GITHUB_REDIRECT_URI=http://localhost:3000/api/auth/github/callback
|
||||
|
||||
# Email
|
||||
SMTP_HOST=smtp.sendgrid.net
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=apikey
|
||||
SMTP_PASSWORD=your-sendgrid-api-key
|
||||
FROM_EMAIL=[email protected]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Routes
|
||||
|
||||
```typescript
|
||||
// routes/auth.routes.ts
|
||||
import { Router } from 'express'
|
||||
import { AuthController } from '../controllers/auth.controller'
|
||||
import { authenticate } from '../middleware/auth.middleware'
|
||||
import { authLimiter } from '../middleware/rate-limit'
|
||||
|
||||
const router = Router()
|
||||
const authController = new AuthController()
|
||||
|
||||
// Registration & Login
|
||||
router.post('/register', authController.register)
|
||||
router.post('/login', authLimiter, authController.login)
|
||||
router.post('/refresh', authController.refreshToken)
|
||||
router.post('/logout', authenticate, authController.logout)
|
||||
|
||||
// Email Verification
|
||||
router.post('/verify-email', authController.verifyEmail)
|
||||
router.post('/resend-verification', authController.resendVerification)
|
||||
|
||||
// Password Reset
|
||||
router.post('/forgot-password', authLimiter, authController.forgotPassword)
|
||||
router.post('/reset-password', authController.resetPassword)
|
||||
|
||||
// OAuth
|
||||
router.get('/google', authController.googleLogin)
|
||||
router.get('/google/callback', authController.googleCallback)
|
||||
router.get('/github', authController.githubLogin)
|
||||
router.get('/github/callback', authController.githubCallback)
|
||||
|
||||
// Profile
|
||||
router.get('/me', authenticate, authController.getProfile)
|
||||
router.patch('/me', authenticate, authController.updateProfile)
|
||||
router.post('/change-password', authenticate, authController.changePassword)
|
||||
|
||||
export default router
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/env-config-setup` - Generate environment config
|
||||
- `/express-api-scaffold` - Generate Express API
|
||||
- `/fastapi-scaffold` - Generate FastAPI
|
||||
|
||||
---
|
||||
|
||||
**Secure authentication. Easy integration. Production-ready.**
|
||||
Reference in New Issue
Block a user