Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung
Features: - Backend: Node.js/Express mit MySQL/MariaDB - Frontend: Vue.js 3 mit Composition API - UTC-Zeithandling für korrekte Zeiterfassung - Timewish-basierte Überstundenberechnung - Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling - Bereinigtes Arbeitsende (Generell/Woche) - Überstunden-Offset für historische Daten - Fixed Layout mit scrollbarem Content - Kompakte UI mit grünem Theme
This commit is contained in:
209
backend/src/services/OAuthService.js
Normal file
209
backend/src/services/OAuthService.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const database = require('../config/database');
|
||||
|
||||
/**
|
||||
* OAuth Service
|
||||
* Verwaltet OAuth/SSO-Logins (Google, GitHub, etc.)
|
||||
*/
|
||||
class OAuthService {
|
||||
constructor() {
|
||||
this.jwtSecret = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
this.jwtExpiration = process.env.JWT_EXPIRATION || '24h';
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth-Login/Registrierung
|
||||
* @param {Object} profile - OAuth-Provider-Profil
|
||||
* @param {string} provider - Provider-Name (google, github, etc.)
|
||||
* @returns {Promise<Object>} Token und Benutzer-Info
|
||||
*/
|
||||
async authenticateWithProvider(profile, provider) {
|
||||
const { User, AuthInfo, AuthIdentity, AuthToken } = database.getModels();
|
||||
|
||||
const providerId = profile.id;
|
||||
const email = profile.emails && profile.emails[0] ? profile.emails[0].value : null;
|
||||
const displayName = profile.displayName || profile.name || email;
|
||||
|
||||
if (!providerId) {
|
||||
throw new Error('OAuth Provider-ID fehlt');
|
||||
}
|
||||
|
||||
// Prüfen ob OAuth-Identity bereits existiert
|
||||
let authIdentity = await AuthIdentity.findOne({
|
||||
where: {
|
||||
provider,
|
||||
identity: providerId
|
||||
},
|
||||
include: [{
|
||||
model: database.getModels().AuthInfo,
|
||||
as: 'authInfo',
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
let user, authInfo;
|
||||
|
||||
if (authIdentity && authIdentity.authInfo) {
|
||||
// Bestehender OAuth-Benutzer
|
||||
authInfo = authIdentity.authInfo;
|
||||
user = authInfo.user;
|
||||
} else {
|
||||
// Neuer OAuth-Benutzer oder Verknüpfung mit bestehendem Account
|
||||
|
||||
// Prüfen ob Benutzer mit dieser E-Mail bereits existiert
|
||||
if (email) {
|
||||
authInfo = await AuthInfo.findOne({
|
||||
where: { email },
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user'
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
if (authInfo) {
|
||||
// Verknüpfe OAuth mit bestehendem Account
|
||||
user = authInfo.user;
|
||||
|
||||
authIdentity = await AuthIdentity.create({
|
||||
auth_info_id: authInfo.id,
|
||||
provider,
|
||||
identity: providerId,
|
||||
version: 0
|
||||
});
|
||||
} else {
|
||||
// Neuen Benutzer erstellen
|
||||
user = await User.create({
|
||||
full_name: displayName,
|
||||
role: 0,
|
||||
daily_hours: 8,
|
||||
week_hours: 40,
|
||||
week_workdays: 5,
|
||||
preferred_title_type: 0,
|
||||
version: 0,
|
||||
last_change: new Date()
|
||||
});
|
||||
|
||||
// Auth-Info erstellen (ohne Passwort für OAuth-only Accounts)
|
||||
authInfo = await AuthInfo.create({
|
||||
user_id: user.id,
|
||||
email: email || `${provider}_${providerId}@oauth.local`,
|
||||
password_hash: '', // Kein Passwort für OAuth-only
|
||||
password_method: 'oauth',
|
||||
password_salt: '',
|
||||
status: 1,
|
||||
failed_login_attempts: 0,
|
||||
email_token: '',
|
||||
email_token_role: 0,
|
||||
unverified_email: '',
|
||||
version: 0
|
||||
});
|
||||
|
||||
// OAuth-Identity erstellen
|
||||
authIdentity = await AuthIdentity.create({
|
||||
auth_info_id: authInfo.id,
|
||||
provider,
|
||||
identity: providerId,
|
||||
version: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// JWT Token generieren
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
email: authInfo.email,
|
||||
role: user.role,
|
||||
provider
|
||||
},
|
||||
this.jwtSecret,
|
||||
{ expiresIn: this.jwtExpiration }
|
||||
);
|
||||
|
||||
// Token in Datenbank speichern
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||
|
||||
await AuthToken.create({
|
||||
auth_info_id: authInfo.id,
|
||||
value: crypto.createHash('sha256').update(token).digest('hex'),
|
||||
expires: expiresAt,
|
||||
version: 0
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
full_name: user.full_name,
|
||||
email: authInfo.email,
|
||||
role: user.role,
|
||||
provider
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth-Identity für Benutzer abrufen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @returns {Promise<Array>} Liste verknüpfter OAuth-Provider
|
||||
*/
|
||||
async getUserIdentities(userId) {
|
||||
const { AuthInfo, AuthIdentity } = database.getModels();
|
||||
|
||||
const authInfo = await AuthInfo.findOne({
|
||||
where: { user_id: userId },
|
||||
include: [{
|
||||
model: AuthIdentity,
|
||||
as: 'identities'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!authInfo || !authInfo.identities) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return authInfo.identities.map(identity => ({
|
||||
provider: identity.provider,
|
||||
identity: identity.identity,
|
||||
id: identity.id
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth-Identity entfernen
|
||||
* @param {number} userId - Benutzer-ID
|
||||
* @param {string} provider - Provider-Name
|
||||
* @returns {Promise<boolean>} Erfolg
|
||||
*/
|
||||
async unlinkProvider(userId, provider) {
|
||||
const { AuthInfo, AuthIdentity } = database.getModels();
|
||||
|
||||
const authInfo = await AuthInfo.findOne({
|
||||
where: { user_id: userId }
|
||||
});
|
||||
|
||||
if (!authInfo) {
|
||||
throw new Error('Benutzer nicht gefunden');
|
||||
}
|
||||
|
||||
const deleted = await AuthIdentity.destroy({
|
||||
where: {
|
||||
auth_info_id: authInfo.id,
|
||||
provider
|
||||
}
|
||||
});
|
||||
|
||||
return deleted > 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new OAuthService();
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user