from flask import Blueprint, request, jsonify, g from app import db from app.models.vault_item import VaultItem, ItemType from app.models.audit_log import AuditLog from app.services.auth_service import require_jwt vault_bp = Blueprint('vault', __name__) VALID_TYPES = {t.value for t in ItemType} def _client_ip(): from flask import request as _req return _req.headers.get('X-Forwarded-For', _req.remote_addr or '').split(',')[0].strip() @vault_bp.route('', methods=['GET']) @require_jwt def list_items(): items = VaultItem.query.filter_by(user_id=g.current_user_id).order_by( VaultItem.name.asc() ).all() return jsonify([item.to_dict() for item in items]), 200 @vault_bp.route('', methods=['POST']) @require_jwt def create_item(): data = request.get_json(silent=True) or {} name = (data.get('name') or '').strip() item_type = data.get('item_type', 'password') enc_data = data.get('enc_data', '') iv = data.get('iv', '') folder_id = data.get('folder_id') if not name: return jsonify({'error': 'name is required'}), 400 if item_type not in VALID_TYPES: return jsonify({'error': f'item_type must be one of {sorted(VALID_TYPES)}'}), 400 if not enc_data or not iv: return jsonify({'error': 'enc_data and iv are required'}), 400 item = VaultItem( user_id=g.current_user_id, folder_id=folder_id, item_type=item_type, # stored as plain string name=name, enc_data=enc_data, iv=iv, ) try: db.session.add(item) db.session.flush() # populate item.id before logging AuditLog.log( user_id=g.current_user_id, action='vault_item.create', resource_type='vault_item', resource_id=item.id, detail=f'Created {item_type} item: "{name}"', ip_address=_client_ip(), ) db.session.commit() except Exception as e: db.session.rollback() return jsonify({'error': f'Database error: {str(e)}'}), 500 return jsonify(item.to_dict()), 201 @vault_bp.route('/', methods=['GET']) @require_jwt def get_item(item_id): item = VaultItem.query.filter_by(id=item_id, user_id=g.current_user_id).first() if not item: return jsonify({'error': 'Item not found'}), 404 return jsonify(item.to_dict()), 200 @vault_bp.route('/', methods=['PUT']) @require_jwt def update_item(item_id): item = VaultItem.query.filter_by(id=item_id, user_id=g.current_user_id).first() if not item: return jsonify({'error': 'Item not found'}), 404 data = request.get_json(silent=True) or {} if 'name' in data: name = data['name'].strip() if not name: return jsonify({'error': 'name cannot be empty'}), 400 item.name = name if 'folder_id' in data: item.folder_id = data['folder_id'] if 'enc_data' in data: item.enc_data = data['enc_data'] if 'iv' in data: item.iv = data['iv'] db.session.flush() AuditLog.log( user_id=g.current_user_id, action='vault_item.update', resource_type='vault_item', resource_id=item.id, detail=f'Updated item: "{item.name}"', ip_address=_client_ip(), ) db.session.commit() return jsonify(item.to_dict()), 200 @vault_bp.route('/', methods=['DELETE']) @require_jwt def delete_item(item_id): item = VaultItem.query.filter_by(id=item_id, user_id=g.current_user_id).first() if not item: return jsonify({'error': 'Item not found'}), 404 item_name = item.name item_id = item.id db.session.delete(item) db.session.flush() AuditLog.log( user_id=g.current_user_id, action='vault_item.delete', resource_type='vault_item', resource_id=item_id, detail=f'Deleted item: "{item_name}"', ip_address=_client_ip(), ) db.session.commit() return jsonify({'message': 'Item deleted'}), 200