Initial commit
This commit is contained in:
195
backend/src/routes/auth.js
Normal file
195
backend/src/routes/auth.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import { Router } from 'express';
|
||||
import { authService } from '../services/auth.js';
|
||||
import { authenticate } from '../middleware/auth.js';
|
||||
import { validateBody, signupSchema, loginSchema, nostrLoginSchema } from '../utils/validation.js';
|
||||
import { prisma } from '../config/database.js';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Signup
|
||||
router.post('/signup', validateBody(signupSchema), async (req, res, next) => {
|
||||
try {
|
||||
const result = await authService.signup(req.body);
|
||||
|
||||
// Set refresh token as httpOnly cookie
|
||||
res.cookie('refresh_token', result.refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
user: result.user,
|
||||
accessToken: result.accessToken,
|
||||
expiresIn: result.expiresIn,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post('/login', validateBody(loginSchema), async (req, res, next) => {
|
||||
try {
|
||||
const result = await authService.login(req.body);
|
||||
|
||||
res.cookie('refresh_token', result.refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
res.json({
|
||||
user: result.user,
|
||||
accessToken: result.accessToken,
|
||||
expiresIn: result.expiresIn,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh token
|
||||
router.post('/refresh', async (req, res, next) => {
|
||||
try {
|
||||
const refreshToken = req.cookies?.refresh_token || req.body.refreshToken;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(401).json({ error: 'Refresh token required' });
|
||||
}
|
||||
|
||||
const result = await authService.refreshTokens(refreshToken);
|
||||
|
||||
res.json({
|
||||
user: result.user,
|
||||
accessToken: result.accessToken,
|
||||
expiresIn: result.expiresIn,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', async (req, res, next) => {
|
||||
try {
|
||||
const refreshToken = req.cookies?.refresh_token;
|
||||
await authService.logout(refreshToken);
|
||||
|
||||
res.clearCookie('refresh_token');
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Get current user
|
||||
router.get('/me', authenticate, async (req, res) => {
|
||||
res.json({ user: req.user });
|
||||
});
|
||||
|
||||
// Update profile
|
||||
router.patch('/me', authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { displayName, lightningAddress, avatarUrl } = req.body;
|
||||
|
||||
const updated = await prisma.user.update({
|
||||
where: { id: req.user.id },
|
||||
data: {
|
||||
displayName,
|
||||
lightningAddress,
|
||||
avatarUrl,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
displayName: true,
|
||||
avatarUrl: true,
|
||||
role: true,
|
||||
lightningAddress: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
res.json({ user: updated });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Nostr challenge
|
||||
router.post('/nostr/challenge', async (req, res) => {
|
||||
const challenge = randomBytes(32).toString('hex');
|
||||
|
||||
// Store challenge temporarily (in production, use Redis with TTL)
|
||||
// For now, we'll include it in the response and verify the signature
|
||||
res.json({
|
||||
challenge,
|
||||
message: `Sign this message to login to LNPaywall: ${challenge}`,
|
||||
});
|
||||
});
|
||||
|
||||
// Nostr verify
|
||||
router.post('/nostr/verify', validateBody(nostrLoginSchema), async (req, res, next) => {
|
||||
try {
|
||||
const result = await authService.nostrLogin(req.body);
|
||||
|
||||
res.cookie('refresh_token', result.refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
res.json({
|
||||
user: result.user,
|
||||
accessToken: result.accessToken,
|
||||
expiresIn: result.expiresIn,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// OAuth start (redirect to provider)
|
||||
router.get('/oauth/:provider/start', (req, res) => {
|
||||
const { provider } = req.params;
|
||||
const { redirect } = req.query;
|
||||
|
||||
// Store redirect URL in session/cookie for after OAuth callback
|
||||
if (redirect) {
|
||||
res.cookie('oauth_redirect', redirect, {
|
||||
httpOnly: true,
|
||||
maxAge: 10 * 60 * 1000, // 10 minutes
|
||||
});
|
||||
}
|
||||
|
||||
// In production, implement full OAuth flow
|
||||
// For now, return instructions
|
||||
res.json({
|
||||
message: `OAuth ${provider} not fully implemented. Use email/password or Nostr login.`,
|
||||
supported: ['google', 'github'],
|
||||
});
|
||||
});
|
||||
|
||||
// OAuth callback
|
||||
router.get('/oauth/:provider/callback', async (req, res, next) => {
|
||||
try {
|
||||
const { provider } = req.params;
|
||||
const { code } = req.query;
|
||||
|
||||
// In production, exchange code for tokens and get user info
|
||||
res.json({
|
||||
message: `OAuth ${provider} callback received`,
|
||||
code: code ? 'received' : 'missing',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user