286 lines
7.8 KiB
JavaScript
286 lines
7.8 KiB
JavaScript
const { OAuth2Client } = require('google-auth-library');
|
|
const jwt = require('jsonwebtoken');
|
|
const User = require('../models/user');
|
|
require('dotenv').config();
|
|
|
|
// Google Client ID should be in env, but for now assuming it handles verification
|
|
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
|
|
|
|
const generateTokens = (user) => {
|
|
const payload = { id: user.id, email: user.email, nickname: user.nickname };
|
|
|
|
const accessToken = jwt.sign(payload, process.env.JWT_SECRET, {
|
|
expiresIn: '1h',
|
|
});
|
|
|
|
const refreshToken = jwt.sign({ id: user.id }, process.env.JWT_REFRESH_SECRET, {
|
|
expiresIn: '14d',
|
|
});
|
|
|
|
return { accessToken, refreshToken };
|
|
};
|
|
|
|
// Generic Social Login Logic
|
|
const socialLogin = async (provider, socialInfo, res) => {
|
|
try {
|
|
const { socialId, email, nickname } = socialInfo;
|
|
|
|
// Find or Create User
|
|
const [user, created] = await User.findOrCreate({
|
|
where: { provider, socialId },
|
|
defaults: { email, nickname },
|
|
});
|
|
|
|
if (!created) {
|
|
// Update info if needed (e.g., changed nickname on social side) - Optional
|
|
user.email = email || user.email;
|
|
user.nickname = nickname || user.nickname;
|
|
}
|
|
|
|
// Generate Tokens
|
|
const { accessToken, refreshToken } = generateTokens(user);
|
|
|
|
// Save Refresh Token to DB
|
|
user.refreshToken = refreshToken;
|
|
await user.save();
|
|
|
|
console.log(`[Auth] User ${user.id} logged in via ${provider}`);
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
accessToken,
|
|
refreshToken,
|
|
isNewUser: created, // 신규 가입 여부
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
nickname: user.nickname,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('[Auth Error]', error);
|
|
return res.status(500).json({ success: false, message: 'Internal Server Error' });
|
|
}
|
|
};
|
|
|
|
// Google Login Handler
|
|
// Google Login Handler (Check only, or Login if exists)
|
|
exports.loginWithGoogle = async (req, res) => {
|
|
const { idToken } = req.body;
|
|
|
|
if (!idToken) {
|
|
return res.status(400).json({ success: false, message: 'Missing idToken' });
|
|
}
|
|
|
|
try {
|
|
// Verify Google Token
|
|
const ticket = await googleClient.verifyIdToken({
|
|
idToken,
|
|
audience: [
|
|
process.env.GOOGLE_CLIENT_ID,
|
|
process.env.GOOGLE_ANDROID_CLIENT_ID
|
|
],
|
|
});
|
|
|
|
const payload = ticket.getPayload();
|
|
const socialId = payload.sub;
|
|
|
|
// Check if user exists
|
|
const user = await User.findOne({ where: { provider: 'google', socialId } });
|
|
|
|
if (user) {
|
|
// User exists -> Login
|
|
const { accessToken, refreshToken } = generateTokens(user);
|
|
user.refreshToken = refreshToken;
|
|
await user.save();
|
|
|
|
console.log(`[Auth] User ${user.id} logged in via google`);
|
|
return res.status(200).json({
|
|
success: true,
|
|
accessToken,
|
|
refreshToken,
|
|
isNewUser: false,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
nickname: user.nickname,
|
|
},
|
|
});
|
|
} else {
|
|
// User does not exist -> Return isNewUser: true (Do NOT create yet)
|
|
return res.status(200).json({
|
|
success: true,
|
|
isNewUser: true,
|
|
// Optional: Return partial info if needed for UI (e.g. pre-filling name)
|
|
email: payload.email,
|
|
nickname: payload.name,
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('[Google Verify Error]', error);
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid Google Token',
|
|
debug: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
// Google Register Handler (Create User)
|
|
exports.registerWithGoogle = async (req, res) => {
|
|
const { idToken } = req.body;
|
|
|
|
if (!idToken) {
|
|
return res.status(400).json({ success: false, message: 'Missing idToken' });
|
|
}
|
|
|
|
try {
|
|
// Verify Google Token again
|
|
const ticket = await googleClient.verifyIdToken({
|
|
idToken,
|
|
audience: [
|
|
process.env.GOOGLE_CLIENT_ID,
|
|
process.env.GOOGLE_ANDROID_CLIENT_ID
|
|
],
|
|
});
|
|
|
|
const payload = ticket.getPayload();
|
|
const socialId = payload.sub;
|
|
const email = payload.email;
|
|
const nickname = payload.name;
|
|
|
|
// Find or Create User
|
|
const [user, created] = await User.findOrCreate({
|
|
where: { provider: 'google', socialId },
|
|
defaults: { email, nickname },
|
|
});
|
|
|
|
// If existing user calls register, just log them in (idempotent)
|
|
const { accessToken, refreshToken } = generateTokens(user);
|
|
user.refreshToken = refreshToken;
|
|
await user.save();
|
|
|
|
console.log(`[Auth] User ${user.id} registered/logged in via google`);
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
accessToken,
|
|
refreshToken,
|
|
isNewUser: created,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
nickname: user.nickname,
|
|
},
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[Google Register Error]', error);
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid Google Token',
|
|
debug: error.message
|
|
});
|
|
}
|
|
};
|
|
|
|
// Test Login Handler (For verification only)
|
|
exports.testLogin = async (req, res) => {
|
|
const { email, nickname } = req.body;
|
|
|
|
if (!email || !nickname) {
|
|
return res.status(400).json({ success: false, message: 'Missing email or nickname' });
|
|
}
|
|
|
|
// Use 'google' as provider to satisfy DB Enum constraint
|
|
const socialId = `test_${email}`;
|
|
|
|
return socialLogin('google', { socialId, email, nickname }, res);
|
|
};
|
|
|
|
// Refresh Token Handler
|
|
exports.refreshToken = async (req, res) => {
|
|
const { refreshToken } = req.body;
|
|
|
|
if (!refreshToken) {
|
|
return res.status(400).json({ success: false, message: 'Refresh Token required' });
|
|
}
|
|
|
|
try {
|
|
const secret = process.env.JWT_REFRESH_SECRET;
|
|
// 1. Verify Refresh Token
|
|
const decoded = jwt.verify(refreshToken, secret);
|
|
|
|
// 2. Check DB
|
|
const user = await User.findByPk(decoded.id);
|
|
if (!user || user.refreshToken !== refreshToken) {
|
|
return res.status(403).json({ success: false, message: 'Invalid Refresh Token' });
|
|
}
|
|
|
|
// 3. Issue new Access Token
|
|
const payload = { id: user.id, email: user.email, nickname: user.nickname };
|
|
const newAccessToken = jwt.sign(payload, process.env.JWT_SECRET, {
|
|
expiresIn: '1h',
|
|
});
|
|
|
|
console.log(`[Auth] Access Token refreshed for User ${user.id}`);
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
accessToken: newAccessToken,
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[Refresh Error]', error);
|
|
if (error.name === 'TokenExpiredError') {
|
|
return res.status(403).json({ success: false, message: 'Refresh Token expired' });
|
|
}
|
|
return res.status(403).json({ success: false, message: 'Invalid Refresh Token' });
|
|
}
|
|
};
|
|
|
|
// Get User Info
|
|
exports.getMe = async (req, res) => {
|
|
try {
|
|
// req.user is set by middleware
|
|
const user = await User.findByPk(req.user.id);
|
|
if (!user) {
|
|
return res.status(404).json({ success: false, message: 'User not found' });
|
|
}
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
nickname: user.nickname,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('[GetMe Error]', error);
|
|
return res.status(500).json({ success: false, message: 'Internal Server Error' });
|
|
}
|
|
};
|
|
|
|
// Withdraw (Delete Account)
|
|
exports.withdraw = async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
const user = await User.findByPk(userId);
|
|
|
|
if (!user) {
|
|
return res.status(404).json({ success: false, message: 'User not found' });
|
|
}
|
|
|
|
// Hard Delete
|
|
await user.destroy();
|
|
|
|
console.log(`[Auth] User ${userId} withdrew from the service.`);
|
|
return res.status(200).json({ success: true, message: 'Account deleted successfully' });
|
|
} catch (error) {
|
|
console.error('[Withdraw Error]', error);
|
|
return res.status(500).json({ success: false, message: 'Internal Server Error' });
|
|
}
|
|
};
|