"""One-line bootstrap install tokens. An admin mints a short-lived token in the UI and hands a tech one command. The token is only ever an authorization to FETCH the bootstrap script and the keyless agent package: it carries no fleet key, provisions no credential, and the PC it installs still has to be approved in Settings > Endpoints > Enrollment before it can report anything. Only the sha256 of the secret is stored, plus a short display prefix, the same shape as EndpointCredential. A token is spent per package download (usecount against maxuses) and dies at expiresat or when an admin revokes it. """ from datetime import datetime from shopdb.api import db def _utciso(value): return value.isoformat() + 'Z' if value else None class EndpointBootstrapToken(db.Model): """A short-lived authorization to fetch the bootstrap installer.""" __tablename__ = 'endpointbootstraptokens' tokenid = db.Column(db.Integer, primary_key=True) tokenhash = db.Column(db.CHAR(64), nullable=False, unique=True) tokenprefix = db.Column(db.String(12), nullable=False) createdat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) createdby = db.Column(db.String(64), nullable=True) expiresat = db.Column(db.DateTime, nullable=False) maxuses = db.Column(db.Integer, nullable=False, default=1) usecount = db.Column(db.Integer, nullable=False, default=0, server_default='0') revokedat = db.Column(db.DateTime, nullable=True) lastusedat = db.Column(db.DateTime, nullable=True) lastusedip = db.Column(db.String(45), nullable=True) def isusable(self, now): """Whether this token may still serve a download at `now`.""" if self.revokedat is not None: return False if self.expiresat is None or self.expiresat <= now: return False return (self.usecount or 0) < (self.maxuses or 0) def state(self, now): if self.revokedat is not None: return 'revoked' if self.expiresat is None or self.expiresat <= now: return 'expired' if (self.usecount or 0) >= (self.maxuses or 0): return 'exhausted' return 'active' def to_dict(self, now=None): now = now or datetime.utcnow() used = self.usecount or 0 maximum = self.maxuses or 0 return { 'tokenid': self.tokenid, 'tokenprefix': self.tokenprefix, 'createdat': _utciso(self.createdat), 'createdby': self.createdby, 'expiresat': _utciso(self.expiresat), 'maxuses': maximum, 'usecount': used, 'remaining': max(maximum - used, 0), 'revokedat': _utciso(self.revokedat), 'lastusedat': _utciso(self.lastusedat), 'lastusedip': self.lastusedip, 'state': self.state(now), } def __repr__(self): return f''