diff --git a/server/database/db.js b/server/database/db.js
index 6fc13477..5d31b41a 100644
--- a/server/database/db.js
+++ b/server/database/db.js
@@ -39,11 +39,11 @@ const userDb = {
},
// Create a new user
- createUser: (username, passwordHash) => {
+ createUser: (username, passwordHash, isPamUser = false) => {
try {
- const stmt = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
- const result = stmt.run(username, passwordHash);
- return { id: result.lastInsertRowid, username };
+ const stmt = db.prepare('INSERT INTO users (username, password_hash, is_pam_user) VALUES (?, ?, ?)');
+ const result = stmt.run(username, passwordHash, isPamUser ? 1 : 0);
+ return { id: result.lastInsertRowid, username, is_pam_user: isPamUser };
} catch (err) {
throw err;
}
@@ -71,11 +71,31 @@ const userDb = {
// Get user by ID
getUserById: (userId) => {
try {
- const row = db.prepare('SELECT id, username, created_at, last_login FROM users WHERE id = ? AND is_active = 1').get(userId);
+ const row = db.prepare('SELECT id, username, created_at, last_login, is_pam_user FROM users WHERE id = ? AND is_active = 1').get(userId);
return row;
} catch (err) {
throw err;
}
+ },
+
+ // Update user PAM status
+ updateUserPamStatus: (userId, isPamUser) => {
+ try {
+ const stmt = db.prepare('UPDATE users SET is_pam_user = ? WHERE id = ?');
+ stmt.run(isPamUser ? 1 : 0, userId);
+ } catch (err) {
+ throw err;
+ }
+ },
+
+ // Check if user is PAM user
+ isPamUser: (userId) => {
+ try {
+ const row = db.prepare('SELECT is_pam_user FROM users WHERE id = ? AND is_active = 1').get(userId);
+ return row ? row.is_pam_user === 1 : false;
+ } catch (err) {
+ throw err;
+ }
}
};
diff --git a/server/database/init.sql b/server/database/init.sql
index bf007b96..9cb07d1c 100644
--- a/server/database/init.sql
+++ b/server/database/init.sql
@@ -5,7 +5,8 @@ PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
+ password_hash TEXT, -- Can be null for PAM users
+ is_pam_user BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_login DATETIME,
is_active BOOLEAN DEFAULT 1
diff --git a/server/routes/auth.js b/server/routes/auth.js
index 82a7c0d8..d83a7f97 100644
--- a/server/routes/auth.js
+++ b/server/routes/auth.js
@@ -2,6 +2,7 @@ import express from 'express';
import bcrypt from 'bcrypt';
import { userDb, db } from '../database/db.js';
import { generateToken, authenticateToken } from '../middleware/auth.js';
+import pamAuth from '../services/pamAuth.js';
const router = express.Router();
@@ -9,8 +10,10 @@ const router = express.Router();
router.get('/status', async (req, res) => {
try {
const hasUsers = await userDb.hasUsers();
- res.json({
+ const pamAvailable = await pamAuth.isAvailable();
+ res.json({
needsSetup: !hasUsers,
+ pamAvailable,
isAuthenticated: false // Will be overridden by frontend if token exists
});
} catch (error) {
@@ -125,6 +128,80 @@ router.get('/user', authenticateToken, (req, res) => {
});
});
+// PAM authentication endpoint
+router.post('/pam-login', async (req, res) => {
+ try {
+ const { username, password } = req.body;
+
+ // Validate input
+ if (!username || !password) {
+ return res.status(400).json({ error: 'Username and password are required' });
+ }
+
+ // Check if PAM is available
+ const pamAvailable = await pamAuth.isAvailable();
+ if (!pamAvailable) {
+ return res.status(501).json({ error: 'PAM authentication is not available on this system' });
+ }
+
+ // Authenticate using PAM
+ const isAuthenticated = await pamAuth.authenticate(username, password);
+
+ if (!isAuthenticated) {
+ return res.status(401).json({ error: 'Invalid username or password' });
+ }
+
+ // Get user info from system
+ const userInfo = await pamAuth.getUserInfo(username);
+
+ // Create or get user from database
+ let user = userDb.getUserByUsername(username);
+
+ if (!user) {
+ // Create new user in database if doesn't exist
+ user = userDb.createUser(username, null, true); // true for PAM user
+ } else {
+ // Update user to be PAM authenticated
+ userDb.updateUserPamStatus(user.id, true);
+ }
+
+ // Generate token
+ const token = generateToken(user);
+
+ // Update last login
+ userDb.updateLastLogin(user.id);
+
+ res.json({
+ success: true,
+ user: {
+ id: user.id,
+ username: user.username,
+ userInfo: userInfo,
+ isPamUser: true
+ },
+ token
+ });
+
+ } catch (error) {
+ console.error('PAM login error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Check PAM availability
+router.get('/pam-status', async (req, res) => {
+ try {
+ const pamAvailable = await pamAuth.isAvailable();
+ res.json({
+ pamAvailable,
+ message: pamAvailable ? 'PAM authentication is available' : 'PAM authentication is not available'
+ });
+ } catch (error) {
+ console.error('PAM status check error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
// Logout (client-side token removal, but this endpoint can be used for logging)
router.post('/logout', authenticateToken, (req, res) => {
// In a simple JWT system, logout is mainly client-side
diff --git a/server/services/pamAuth.js b/server/services/pamAuth.js
new file mode 100644
index 00000000..b5774025
--- /dev/null
+++ b/server/services/pamAuth.js
@@ -0,0 +1,132 @@
+import { spawn } from 'child_process';
+import { promisify } from 'util';
+
+class PAMAuthService {
+ constructor() {
+ this.serviceName = 'login'; // Default PAM service
+ }
+
+ /**
+ * Authenticate a user using PAM via system commands
+ * @param {string} username - The username to authenticate
+ * @param {string} password - The password to verify
+ * @returns {Promise