- Remove old Confluence variables - Add NEXT_PUBLIC_API_URL for API access 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
110 lines
2.7 KiB
JavaScript
110 lines
2.7 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET;
|
|
const ADMIN_USERNAME = process.env.ADMIN_USERNAME;
|
|
const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH;
|
|
|
|
// CORS headers
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': process.env.CORS_ORIGIN || '*',
|
|
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
|
|
'Access-Control-Allow-Methods': 'POST,OPTIONS'
|
|
};
|
|
|
|
// Helper function to create response
|
|
const createResponse = (statusCode, body) => ({
|
|
statusCode,
|
|
headers,
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
// Login handler
|
|
const login = async (event) => {
|
|
try {
|
|
const { username, password } = JSON.parse(event.body);
|
|
|
|
// Validate credentials
|
|
if (username !== ADMIN_USERNAME) {
|
|
return createResponse(401, { error: 'Invalid credentials' });
|
|
}
|
|
|
|
// Compare password with hash
|
|
const isValid = await bcrypt.compare(password, ADMIN_PASSWORD_HASH);
|
|
if (!isValid) {
|
|
return createResponse(401, { error: 'Invalid credentials' });
|
|
}
|
|
|
|
// Generate JWT token
|
|
const token = jwt.sign(
|
|
{ username, role: 'admin' },
|
|
JWT_SECRET,
|
|
{ expiresIn: '24h' }
|
|
);
|
|
|
|
return createResponse(200, {
|
|
token,
|
|
expiresIn: 86400 // 24 hours in seconds
|
|
});
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
return createResponse(500, { error: 'Authentication failed' });
|
|
}
|
|
};
|
|
|
|
// Verify token (for Lambda authorizer)
|
|
const verifyToken = async (event) => {
|
|
try {
|
|
const token = event.authorizationToken?.replace('Bearer ', '');
|
|
|
|
if (!token) {
|
|
throw new Error('Unauthorized');
|
|
}
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
|
|
return {
|
|
principalId: decoded.username,
|
|
policyDocument: {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Action: 'execute-api:Invoke',
|
|
Effect: 'Allow',
|
|
Resource: event.methodArn
|
|
}
|
|
]
|
|
},
|
|
context: {
|
|
username: decoded.username,
|
|
role: decoded.role
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error('Token verification error:', error);
|
|
throw new Error('Unauthorized');
|
|
}
|
|
};
|
|
|
|
// Main handler
|
|
exports.handler = async (event) => {
|
|
const { httpMethod, resource } = event;
|
|
|
|
// Handle CORS preflight
|
|
if (httpMethod === 'OPTIONS') {
|
|
return createResponse(200, {});
|
|
}
|
|
|
|
// Handle login
|
|
if (resource === '/auth/login' && httpMethod === 'POST') {
|
|
return login(event);
|
|
}
|
|
|
|
// Handle token verification (for Lambda authorizer)
|
|
if (event.type === 'TOKEN') {
|
|
return verifyToken(event);
|
|
}
|
|
|
|
return createResponse(404, { error: 'Not found' });
|
|
}; |