Implement Google OAuth linking functionality. Update backend to handle linking existing accounts with Google, including state token management. Enhance frontend to support linking process, including new UI components for user input and feedback. Update mobile app to handle OAuth callbacks and integrate linking features. Refactor related services and controllers for improved error handling and user experience.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
const passport = require('passport');
|
||||
const oauthService = require('../services/OAuthService');
|
||||
|
||||
/**
|
||||
* OAuth Controller
|
||||
@@ -10,12 +11,38 @@ class OAuthController {
|
||||
* GET /api/auth/google
|
||||
*/
|
||||
googleAuth(req, res, next) {
|
||||
const state = req.query.stateToken || oauthService.createStateToken({
|
||||
platform: req.query.platform === 'android' ? 'android' : 'web'
|
||||
});
|
||||
passport.authenticate('google', {
|
||||
scope: ['profile', 'email'],
|
||||
session: false
|
||||
session: false,
|
||||
state
|
||||
})(req, res, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Google OAuth-Verknüpfung für eingeloggte Benutzer starten
|
||||
* POST /api/auth/google/link-url
|
||||
*/
|
||||
createGoogleLinkUrl(req, res) {
|
||||
try {
|
||||
const platform = req.body?.platform === 'android' ? 'android' : 'web';
|
||||
const stateToken = oauthService.createStateToken({
|
||||
mode: 'link',
|
||||
platform,
|
||||
userId: req.user.userId
|
||||
});
|
||||
const baseUrl = process.env.API_PUBLIC_URL || `${req.protocol}://${req.get('host')}/api`;
|
||||
res.json({
|
||||
success: true,
|
||||
url: `${baseUrl}/auth/google?stateToken=${encodeURIComponent(stateToken)}`
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google OAuth Callback
|
||||
* GET /api/auth/google/callback
|
||||
@@ -24,18 +51,67 @@ class OAuthController {
|
||||
passport.authenticate('google', {
|
||||
session: false,
|
||||
failureRedirect: `${process.env.FRONTEND_URL || 'http://localhost:5010'}/login?error=oauth_failed`
|
||||
}, (err, result) => {
|
||||
}, async (err, result) => {
|
||||
if (err || !result) {
|
||||
console.error('Google OAuth Fehler:', err);
|
||||
return res.redirect(`${process.env.FRONTEND_URL || 'http://localhost:5010'}/login?error=oauth_failed`);
|
||||
}
|
||||
|
||||
// Redirect zum Frontend mit Token
|
||||
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5010';
|
||||
res.redirect(`${frontendUrl}/oauth-callback?token=${result.token}`);
|
||||
try {
|
||||
const state = oauthService.verifyStateToken(req.query.state);
|
||||
const authResult = await oauthService.completeOAuthLogin(result.profile, result.provider, {
|
||||
linkUserId: state.mode === 'link' ? state.userId : null
|
||||
});
|
||||
|
||||
const target = state.platform === 'android'
|
||||
? 'timeclock://oauth-callback'
|
||||
: `${process.env.FRONTEND_URL || 'http://localhost:5010'}/oauth-callback`;
|
||||
|
||||
if (authResult.requiresLink) {
|
||||
const params = new URLSearchParams({
|
||||
pending: authResult.pendingToken,
|
||||
email: authResult.email || '',
|
||||
provider: result.provider
|
||||
});
|
||||
return res.redirect(`${target}?${params.toString()}`);
|
||||
}
|
||||
|
||||
return res.redirect(`${target}?token=${encodeURIComponent(authResult.token)}`);
|
||||
} catch (callbackError) {
|
||||
console.error('Google OAuth Callback-Verarbeitung fehlgeschlagen:', callbackError);
|
||||
return res.redirect(`${process.env.FRONTEND_URL || 'http://localhost:5010'}/login?error=oauth_failed`);
|
||||
}
|
||||
})(req, res, next);
|
||||
}
|
||||
|
||||
async linkExistingAccount(req, res) {
|
||||
try {
|
||||
const { pendingToken, email, password } = req.body;
|
||||
if (!pendingToken || !email || !password) {
|
||||
return res.status(400).json({ success: false, error: 'pendingToken, E-Mail und Passwort sind erforderlich' });
|
||||
}
|
||||
const result = await oauthService.linkPendingToPasswordAccount(pendingToken, email, password);
|
||||
res.json({ success: true, token: result.token, user: result.user });
|
||||
} catch (error) {
|
||||
console.error('OAuth-Verknüpfung mit bestehendem Account fehlgeschlagen:', error);
|
||||
res.status(401).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async linkPendingToCurrentUser(req, res) {
|
||||
try {
|
||||
const { pendingToken } = req.body;
|
||||
if (!pendingToken) {
|
||||
return res.status(400).json({ success: false, error: 'pendingToken ist erforderlich' });
|
||||
}
|
||||
const result = await oauthService.linkPendingToAuthenticatedUser(pendingToken, req.user.userId);
|
||||
res.json({ success: true, token: result.token, user: result.user });
|
||||
} catch (error) {
|
||||
console.error('OAuth-Verknüpfung fehlgeschlagen:', error);
|
||||
res.status(400).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth-Identities für Benutzer abrufen
|
||||
* GET /api/auth/identities
|
||||
@@ -43,8 +119,6 @@ class OAuthController {
|
||||
async getIdentities(req, res) {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const oauthService = require('../services/OAuthService');
|
||||
|
||||
const identities = await oauthService.getUserIdentities(userId);
|
||||
|
||||
res.json({
|
||||
@@ -69,8 +143,6 @@ class OAuthController {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { provider } = req.params;
|
||||
const oauthService = require('../services/OAuthService');
|
||||
|
||||
const unlinked = await oauthService.unlinkProvider(userId, provider);
|
||||
|
||||
if (unlinked) {
|
||||
@@ -98,4 +170,3 @@ class OAuthController {
|
||||
module.exports = new OAuthController();
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user