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' }); } };