149 lines
6.8 KiB
JavaScript
149 lines
6.8 KiB
JavaScript
// backend/routes/auth.js
|
|
// Uses HANA ZCUST_USERS table for authentication.
|
|
// Roles:
|
|
// manager → can view list, open detail, verify/reject, fill manager fields
|
|
// sap_adder → all manager perms + approve & push to SAP B1
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const jwt = require('jsonwebtoken');
|
|
const userDb = require('../services/hanaUsers');
|
|
const cryptoUtil = require('../services/cryptoUtil');
|
|
const sap = require('../services/sapServiceLayer');
|
|
const { verifyToken } = require('../middleware/auth');
|
|
|
|
const SECRET = process.env.JWT_SECRET || 'sap-portal-secret';
|
|
const EXPIRES = '12h';
|
|
|
|
// Build the signed JWT payload for a user, embedding their per-user SAP login
|
|
// (SAP password AES-encrypted) so downstream SAP calls can act as them without
|
|
// a per-request DB lookup. `creds` is the decrypted { sapUser, sapPassword }
|
|
// or null when the user hasn't set one.
|
|
function buildPayload(user, creds) {
|
|
return {
|
|
id: user.id, username: user.username, role: user.role,
|
|
name: user.fullName, email: user.email || '',
|
|
sapUser: creds ? creds.sapUser : '',
|
|
sapPwdEnc: creds ? cryptoUtil.encrypt(creds.sapPassword) : '',
|
|
};
|
|
}
|
|
|
|
// ── POST /auth/login ──────────────────────────────────────────────────────────
|
|
router.post('/login', async (req, res) => {
|
|
const { username, password } = req.body;
|
|
if (!username || !password)
|
|
return res.status(400).json({ success: false, message: 'Username and password required' });
|
|
|
|
try {
|
|
const user = await userDb.findByUsername(username);
|
|
if (!user)
|
|
return res.status(401).json({ success: false, message: 'Invalid username or password' });
|
|
|
|
const ok = await userDb.verifyPassword(password, user.passwordHash);
|
|
if (!ok)
|
|
return res.status(401).json({ success: false, message: 'Invalid username or password' });
|
|
|
|
await userDb.touchLastLogin(user.id);
|
|
|
|
const creds = await userDb.getSapCredentials(user.id); // decrypted or null
|
|
const payload = buildPayload(user, creds);
|
|
const token = jwt.sign(payload, SECRET, { expiresIn: EXPIRES });
|
|
|
|
res.json({
|
|
success: true,
|
|
token,
|
|
user: { id: user.id, username: user.username, name: user.fullName, role: user.role, email: user.email, hasSapLogin: !!creds, sapUser: creds ? creds.sapUser : '' },
|
|
});
|
|
} catch (err) {
|
|
console.error('[AUTH] login error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
});
|
|
|
|
// NOTE: central-auth SSO login (POST /sso-login) lives in server.js's own
|
|
// inline authRouter, not here — this file (routes/auth.js) is NOT mounted by
|
|
// server.js (which defines its own, more complete /api/auth router with
|
|
// modules/approvalSteps/per-company sapLogins), so anything added here is
|
|
// dead code. See server.js's authRouter.post('/sso-login', ...).
|
|
|
|
// ── GET /auth/me ──────────────────────────────────────────────────────────────
|
|
router.get('/me', (req, res) => {
|
|
const token = (req.headers.authorization || '').replace('Bearer ', '');
|
|
if (!token) return res.status(401).json({ success: false });
|
|
try {
|
|
const user = jwt.verify(token, SECRET);
|
|
res.json({ success: true, user });
|
|
} catch {
|
|
res.status(401).json({ success: false });
|
|
}
|
|
});
|
|
|
|
// ── GET /auth/profile — full profile from DB ──────────────────────────────────
|
|
router.get('/profile', async (req, res) => {
|
|
const token = (req.headers.authorization || '').replace('Bearer ', '');
|
|
if (!token) return res.status(401).json({ success: false });
|
|
try {
|
|
const decoded = jwt.verify(token, SECRET);
|
|
const user = await userDb.findById(decoded.id);
|
|
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
|
|
res.json({ success: true, user });
|
|
} catch (err) {
|
|
res.status(401).json({ success: false, message: err.message });
|
|
}
|
|
});
|
|
|
|
// ── GET /auth/sap-credentials — own SAP login status (never the password) ─────
|
|
router.get('/sap-credentials', verifyToken, async (req, res) => {
|
|
try {
|
|
const user = await userDb.findById(req.user.id);
|
|
res.json({ success: true, sapUser: user?.sapLoginUser || '', hasSapLogin: !!user?.hasSapLogin });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
// ── PUT /auth/sap-credentials — set own SAP login (validated against SAP) ──────
|
|
// Returns a fresh token carrying the new credentials so they take effect
|
|
// immediately without re-login.
|
|
router.put('/sap-credentials', verifyToken, async (req, res) => {
|
|
const sapUser = (req.body?.sapUser || '').trim();
|
|
const sapPassword = req.body?.sapPassword || '';
|
|
if (!sapUser || !sapPassword)
|
|
return res.status(400).json({ success: false, message: 'SAP User ID and Password are required' });
|
|
try {
|
|
await sap.testSapLogin(null, sapUser, sapPassword); // validate before saving
|
|
} catch (e) {
|
|
return res.status(400).json({ success: false, message: 'SAP login failed — check your SAP User ID/Password. (' + e.message + ')' });
|
|
}
|
|
try {
|
|
await userDb.setSapCredentials(req.user.id, sapUser, sapPassword);
|
|
const user = await userDb.findById(req.user.id);
|
|
const token = jwt.sign(buildPayload(user, { sapUser, sapPassword }), SECRET, { expiresIn: EXPIRES });
|
|
res.json({ success: true, token, sapUser, hasSapLogin: true });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
// ── PUT /auth/profile — update own name / email / password ────────────────────
|
|
router.put('/profile', async (req, res) => {
|
|
const token = (req.headers.authorization || '').replace('Bearer ', '');
|
|
if (!token) return res.status(401).json({ success: false });
|
|
try {
|
|
const decoded = jwt.verify(token, SECRET);
|
|
const { fullName, email, currentPassword, newPassword } = req.body;
|
|
|
|
if (newPassword) {
|
|
const user = await userDb.findByUsername(decoded.username);
|
|
const ok = await userDb.verifyPassword(currentPassword || '', user.passwordHash);
|
|
if (!ok) return res.status(400).json({ success: false, message: 'Current password is incorrect' });
|
|
}
|
|
|
|
const patch = {};
|
|
if (fullName !== undefined) patch.fullName = fullName.trim();
|
|
if (email !== undefined) patch.email = email.trim();
|
|
if (newPassword) patch.password = newPassword;
|
|
|
|
await userDb.updateUser(decoded.id, patch);
|
|
res.json({ success: true, message: 'Profile updated successfully' });
|
|
} catch (err) {
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router; |