Files
PassKeeper/app/routes/vault.py
T

140 lines
4.2 KiB
Python

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')
enc_name = data.get('enc_name', '')
iv_name = data.get('iv_name', '')
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,
enc_name=enc_name or None,
iv_name=iv_name or None,
)
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('/<int:item_id>', 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('/<int:item_id>', 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']
if 'enc_name' in data:
item.enc_name = data['enc_name'] or None
if 'iv_name' in data:
item.iv_name = data['iv_name'] or None
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('/<int:item_id>', 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