import json from datetime import datetime from app import db class ManagedDatabaseUser(db.Model): """A database user/grant ServerKit created on a managed database. Live engines forget nothing, but ServerKit used to: users created through the panel left no durable trace, so restarts lost the mapping between a managed database and the credentials provisioned for it. This row is that trace — it powers the users panel and scopes one-click admin SSO. ``is_shadow`` marks short-lived, single-use credentials (e.g. the Adminer SSO login) that a reaper drops once ``expires_at`` passes. Passwords are NEVER stored here — they cross the API exactly once at creation time. """ __tablename__ = 'managed_database_users' id = db.Column(db.Integer, primary_key=True) managed_database_id = db.Column( db.Integer, db.ForeignKey('managed_databases.id', ondelete='CASCADE'), nullable=False, index=True, ) username = db.Column(db.String(120), nullable=False) # JSON list of grant keywords, e.g. ["ALL"] or ["SELECT", "INSERT"]. grants = db.Column(db.Text, nullable=False, default='["ALL"]') is_shadow = db.Column(db.Boolean, nullable=False, default=False) expires_at = db.Column(db.DateTime, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # Deleting the managed database removes its user rows (ORM cascade); the # engine-side users are dropped explicitly by the service, never implicitly. managed_database = db.relationship( 'ManagedDatabase', backref=db.backref('users', cascade='all, delete-orphan'), ) __table_args__ = ( db.UniqueConstraint('managed_database_id', 'username', name='uq_managed_db_user'), ) def get_grants(self): try: grants = json.loads(self.grants or '[]') return grants if isinstance(grants, list) else [] except (ValueError, TypeError): return [] def set_grants(self, grants): self.grants = json.dumps(list(grants or [])) @property def is_expired(self): return bool(self.expires_at and self.expires_at < datetime.utcnow()) def to_dict(self, live=None): data = { 'id': self.id, 'managed_database_id': self.managed_database_id, 'username': self.username, 'grants': self.get_grants(), 'is_shadow': self.is_shadow, 'expires_at': self.expires_at.isoformat() if self.expires_at else None, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, } if live is not None: data['present'] = live.get('present') return data def __repr__(self): return f''