Add dual user classes: admin + tech users with PIN login
- Add tech user management (JSON-backed CRUD with PIN auth) - Dual login: tabbed Tech Login (username+PIN) / Admin Login (NC credentials) - Admin panel: tappable user list with detail modal (enable/disable, reset PIN, reset NC password, delete) - Auto-provision Nextcloud accounts for tech users - Admin guard: tech users redirected away from admin panel - New data volume for persistent tech_users.json storage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, request, jsonify, session
|
||||
from app.services.nextcloud import NextcloudClient
|
||||
from app.services import tech_users
|
||||
from config import Config
|
||||
import base64
|
||||
|
||||
@@ -15,42 +16,74 @@ def _decrypt_password(encrypted: str) -> str:
|
||||
|
||||
@bp.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""Authenticate user against Nextcloud."""
|
||||
"""Authenticate admin user against Nextcloud."""
|
||||
data = request.get_json()
|
||||
|
||||
|
||||
if not data or 'username' not in data or 'password' not in data:
|
||||
return jsonify({'error': 'Username and password required'}), 400
|
||||
|
||||
|
||||
username = data['username'].strip()
|
||||
password = data['password']
|
||||
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({'error': 'Username and password cannot be empty'}), 400
|
||||
|
||||
|
||||
try:
|
||||
# Validate credentials by attempting to connect to Nextcloud
|
||||
nc_client = NextcloudClient(Config.NEXTCLOUD_URL, username, password)
|
||||
|
||||
|
||||
if not nc_client.verify_credentials():
|
||||
return jsonify({'error': 'Invalid username or password'}), 401
|
||||
|
||||
# Check if user is admin
|
||||
|
||||
is_admin = nc_client.check_admin()
|
||||
|
||||
# Store credentials in session
|
||||
if not is_admin:
|
||||
return jsonify({'error': 'Admin credentials required. Tech users must use the Tech login.'}), 403
|
||||
|
||||
session['username'] = username
|
||||
session['password'] = _encrypt_password(password)
|
||||
session['is_admin'] = is_admin
|
||||
|
||||
session['is_admin'] = True
|
||||
session['user_type'] = 'admin'
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'username': username,
|
||||
'is_admin': is_admin
|
||||
'is_admin': True,
|
||||
'user_type': 'admin',
|
||||
}), 200
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'Authentication failed: {str(e)}'}), 500
|
||||
|
||||
@bp.route('/login/tech', methods=['POST'])
|
||||
def login_tech():
|
||||
"""Authenticate tech user with username + PIN."""
|
||||
data = request.get_json()
|
||||
|
||||
if not data or 'username' not in data or 'pin' not in data:
|
||||
return jsonify({'error': 'Username and PIN required'}), 400
|
||||
|
||||
username = data['username'].strip()
|
||||
pin = data['pin']
|
||||
|
||||
if not username or not pin:
|
||||
return jsonify({'error': 'Username and PIN cannot be empty'}), 400
|
||||
|
||||
user = tech_users.verify_pin(username, pin)
|
||||
if not user:
|
||||
return jsonify({'error': 'Invalid username or PIN'}), 401
|
||||
|
||||
# Store the generated NC password so API calls work transparently
|
||||
session['username'] = username
|
||||
session['password'] = _encrypt_password(user['nc_password'])
|
||||
session['is_admin'] = False
|
||||
session['user_type'] = 'tech'
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'username': username,
|
||||
'is_admin': False,
|
||||
'user_type': 'tech',
|
||||
}), 200
|
||||
|
||||
@bp.route('/logout', methods=['POST'])
|
||||
def logout():
|
||||
"""Clear user session."""
|
||||
@@ -64,7 +97,8 @@ def status():
|
||||
return jsonify({
|
||||
'authenticated': True,
|
||||
'username': session['username'],
|
||||
'is_admin': session.get('is_admin', False)
|
||||
'is_admin': session.get('is_admin', False),
|
||||
'user_type': session.get('user_type', 'admin'),
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
|
||||
Reference in New Issue
Block a user