Files
Bentlybro e876ff796a Store API keys hashed instead of in plaintext
Keys are now stored as a SHA-256 digest in the existing key column (64
chars, no schema change). Lookups hash the presented key; rows still
holding plaintext are matched and upgraded to their hash on first use.
The full key is shown once at creation and the dashboard displays a
masked placeholder thereafter.
2026-07-06 13:41:53 +01:00

60 lines
1.8 KiB
Python

import hashlib
import secrets
import uuid
from datetime import datetime
from app import db
class APIKey(db.Model):
__tablename__ = 'api_keys'
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
key = db.Column(db.String(64), unique=True, nullable=False)
name = db.Column(db.String(100), nullable=False)
project_id = db.Column(db.String(36), db.ForeignKey(
'projects.id'), nullable=False)
is_active = db.Column(db.Boolean, default=True)
last_used = db.Column(db.DateTime)
usage_count = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
plaintext_key = None
def __init__(self, **kwargs):
super(APIKey, self).__init__(**kwargs)
if not self.key:
raw = self.generate_key()
self.plaintext_key = raw
self.key = self.hash_key(raw)
@staticmethod
def generate_key():
return f"am_{secrets.token_urlsafe(32)}"
@staticmethod
def hash_key(raw_key):
return hashlib.sha256(raw_key.strip().encode('utf-8')).hexdigest()
def increment_usage(self):
self.usage_count += 1
self.last_used = datetime.utcnow()
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'project_id': self.project_id,
'is_active': self.is_active,
'last_used': self.last_used.isoformat() if self.last_used else None,
'usage_count': self.usage_count,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()
}
def __repr__(self):
return f'<APIKey {self.name}>'