class Notification(db.Model, BaseModel): """Model for Notification""" __tablename__ = 'notifications' # columns user_id = db.Column('user_id', db.Integer, db.ForeignKey('users.id'), nullable=True) channel = db.Column('channel', db.Integer, nullable=False) template = db.Column('template', db.String(60), nullable=True) service = db.Column('service', db.String(60), nullable=True) notification_id = db.Column('notification_id', db.String(60), nullable=True) accepted = db.Column('accepted', db.Integer, nullable=False) rejected = db.Column('rejected', db.Integer, nullable=False) sent_at = db.Column('sent_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) # relationships user = db.relationship('User', back_populates='notifications')
class AdministratorPasswordHistory(db.Model): """Model for AdministratorPasswordHistory""" __tablename__ = 'administrator_password_history' # columns id = db.Column('id', db.BigInteger, primary_key=True) administrator_id = db.Column('administrator_id', db.Integer, db.ForeignKey('administrators.id'), nullable=True) password = db.Column('password', db.String(60), nullable=False) set_date = db.Column('set_date', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) # timestamps created_at = db.Column('created_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) updated_at = db.Column('updated_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), onupdate=db.func.current_timestamp(), nullable=False) # relationships administrator = db.relationship('Administrator', back_populates='password_history')
class UserProfile(db.Model, BaseModel): """Model for UserProfile""" __tablename__ = 'user_profiles' CRYPT_SYM_SECRET_KEY = Config.CRYPT_SYM_SECRET_KEY # columns user_id = db.Column('user_id', db.Integer, db.ForeignKey('users.id'), nullable=False) first_name = db.Column('first_name', PGPString(CRYPT_SYM_SECRET_KEY, length=200), nullable=False) last_name = db.Column('last_name', PGPString(CRYPT_SYM_SECRET_KEY, length=200), nullable=False) joined_at = db.Column('joined_at', db.TIMESTAMP(timezone=True), index=True, server_default=db.func.current_timestamp(), nullable=False) # relationships user = db.relationship('User', back_populates='profile')
class Country(db.Model, BaseModel): """Model for Country""" __tablename__ = 'countries' # columns name = db.Column('name', db.String(60), unique=True, nullable=False) code_2 = db.Column('code_2', db.String(2), unique=True, nullable=False) code_3 = db.Column('code_3', db.String(3), unique=True, nullable=False) # relationships regions = db.relationship("Region", back_populates="country")
class UserTermsOfService(db.Model): """Model for UserTermsOfService""" __tablename__ = 'user_terms_of_services' CRYPT_SYM_SECRET_KEY = Config.CRYPT_SYM_SECRET_KEY # columns user_id = db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True) terms_of_service_id = db.Column('terms_of_service_id', db.Integer, db.ForeignKey('terms_of_services.id'), primary_key=True) accept_date = db.Column('accept_date', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) ip_address = db.Column('ip_address', PGPString(CRYPT_SYM_SECRET_KEY, length=200), nullable=False) # relationships user = db.relationship('User', uselist=False) terms_of_service = db.relationship('TermsOfService', uselist=False) # timestamps created_at = db.Column('created_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) updated_at = db.Column('updated_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), onupdate=db.func.current_timestamp(), nullable=False)
class Region(db.Model, BaseModel): """Model for Region""" __tablename__ = 'regions' # columns name = db.Column('name', db.String(60), nullable=False) code_2 = db.Column('code_2', db.String(2)) country_id = db.Column('country_id', db.Integer, db.ForeignKey('countries.id'), nullable=False) # relationships country = db.relationship('Country', back_populates="regions")
class PasswordReset(db.Model, BaseModel): """Model for PasswordReset""" __tablename__ = 'password_resets' # columns user_id = db.Column('user_id', db.Integer, db.ForeignKey('users.id'), nullable=False) code = db.Column('code', db.String(40), nullable=False) is_used = db.Column('is_used', db.Boolean, nullable=False) requested_at = db.Column('requested_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) ip_address = db.Column('ip_address', db.String(50), index=True) # relationships user = db.relationship('User', back_populates='password_resets')
class Administrator(db.Model, BaseModel): """Model for Administrator""" __tablename__ = 'administrators' HASH_ROUNDS = Config.AUTH_HASH_ROUNDS AUTH_SECRET_KEY = Config.AUTH_SECRET_KEY CRYPT_SYM_SECRET_KEY = Config.CRYPT_SYM_SECRET_KEY CRYPT_DIGEST_SALT = Config.CRYPT_DIGEST_SALT # columns username = db.Column('username', db.String(40), index=True, unique=True, nullable=False) _email = db.Column('email', PGPString(CRYPT_SYM_SECRET_KEY, length=500), nullable=False) email_digest = db.Column('email_digest', db.String(64), unique=True, nullable=False) first_name = db.Column('first_name', PGPString(CRYPT_SYM_SECRET_KEY, length=200), nullable=False) last_name = db.Column('last_name', PGPString(CRYPT_SYM_SECRET_KEY, length=200), nullable=False) _password = db.Column('password', db.String(60), nullable=False) password_changed_at = db.Column('password_changed_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) joined_at = db.Column('joined_at', db.TIMESTAMP(timezone=True), server_default=db.func.current_timestamp(), nullable=False) # relationships roles = db.relationship('Role', secondary=roles, lazy='subquery', order_by="Role.priority", backref=db.backref('administrators', lazy=True)) password_history = db.relationship( 'AdministratorPasswordHistory', cascade="all,delete-orphan", back_populates='administrator', order_by=AdministratorPasswordHistory.set_date.desc()) @hybrid_property def password(self): """Gets `password` property (hashed). :return: User's hashed password value :rtype: string """ return self._password @password.setter def password(self, password): """Sets `password` property. Applies Bcrypt hashing function to `password` before storing it. The number of hashing rounds are configurable in the main application config settings. :param password: User's plaintext password :type password: str """ self._password = str( bcrypt.hashpw(bytes(password, 'utf-8'), bcrypt.gensalt(self.HASH_ROUNDS)), 'utf8') self.password_changed_at = datetime.now() @hybrid_property def email(self): """Gets `email` property. :return: User's plaintext email address :rtype: str """ return self._email @email.setter def email(self, email): """Sets `email` property. Applies a lowercase transformation to `email` before storing it. Also sets the `email_digest` property to its SHA-256 hash value - this is useful if the email is stored encrypted, to allow lookups and comparisons (e.g.: duplicates) if an exact match is supplied. :param email: User's plaintext email address :type email: str """ self._email = email.lower() hash_object = hashlib.sha256( (self.CRYPT_DIGEST_SALT + email).encode('utf-8')) self.email_digest = hash_object.hexdigest() def check_password(self, password): """Checks supplied password against saved value. :param password: User's plaintext password :type password: str :return: True if password matches what's on file, False otherwise :rtype: bool """ return bcrypt.checkpw(password.encode('utf-8'), self._password.encode('utf-8')) def generate_auth_token(self, expiration=1800): """Creates a new authentication token. :param expiration: Length of time in seconds that token is valid :type expiration: int :return: Authentication token :rtype: str """ ser = Serializer(self.AUTH_SECRET_KEY, expires_in=expiration) return ser.dumps({'id': self.id, 'type': 'administrator'}) @staticmethod def verify_auth_token(token): """Verifies authentication token is valid and current. :param token: Authentication token :type token: str :return: The user associated with token if valid, None otherwise :rtype: User | None """ ser = Serializer(Administrator.AUTH_SECRET_KEY) try: data = ser.loads(token) except SignatureExpired: return None # valid token, but expired except BadSignature: return None # invalid token if 'type' in data and data['type'] == 'administrator': admin = Administrator.query.get(data['id']) return admin return None