Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • uffd/uffd
  • rixx/uffd
  • thies/uffd
  • leona/uffd
  • strifel/uffd
  • thies/uffd-2
6 results
Select Git revision
Loading items
Show changes
Showing
with 3315 additions and 545 deletions
......@@ -2,12 +2,94 @@ import datetime
import secrets
import enum
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Enum
from flask import current_app
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Enum, Text, Boolean
from sqlalchemy.orm import relationship
from sqlalchemy.ext.hybrid import hybrid_property
from flask_babel import gettext as _
try:
from ua_parser import user_agent_parser
USER_AGENT_PARSER_SUPPORTED = True
except ImportError:
USER_AGENT_PARSER_SUPPORTED = False
from uffd.database import db
from uffd.utils import token_typeable
from uffd.tasks import cleanup_task
from uffd.password_hash import PasswordHashAttribute, HighEntropyPasswordHash
@cleanup_task.delete_by_attribute('expired')
class Session(db.Model):
__tablename__ = 'session'
id = Column(Integer(), primary_key=True, autoincrement=True)
_secret = Column('secret', Text)
secret = PasswordHashAttribute('_secret', HighEntropyPasswordHash)
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
user = relationship('User', back_populates='sessions')
oauth2_grants = relationship('OAuth2Grant', back_populates='session', cascade='all, delete-orphan')
oauth2_tokens = relationship('OAuth2Token', back_populates='session', cascade='all, delete-orphan')
created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
last_used = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
user_agent = Column(Text, nullable=False, default='')
ip_address = Column(Text)
mfa_done = Column(Boolean(create_constraint=True), default=False, nullable=False)
@hybrid_property
def expired(self):
if self.created is None or self.last_used is None:
return False
if self.created < datetime.datetime.utcnow() - datetime.timedelta(seconds=current_app.config['SESSION_LIFETIME_SECONDS']):
return True
if self.last_used < datetime.datetime.utcnow() - current_app.permanent_session_lifetime:
return True
return False
@expired.expression
def expired(cls): # pylint: disable=no-self-argument
return db.or_(
cls.created < datetime.datetime.utcnow() - datetime.timedelta(seconds=current_app.config['SESSION_LIFETIME_SECONDS']),
cls.last_used < datetime.datetime.utcnow() - current_app.permanent_session_lifetime,
)
@property
def user_agent_browser(self):
# pylint: disable=too-many-return-statements
if USER_AGENT_PARSER_SUPPORTED and not getattr(self, 'DISABLE_USER_AGENT_PARSER', False):
family = user_agent_parser.ParseUserAgent(self.user_agent)['family']
return family if family != 'Other' else _('Unknown')
if ' OPR/' in self.user_agent:
return 'Opera'
if ' Edg/' in self.user_agent:
return 'Microsoft Edge'
if ' Safari/' in self.user_agent and ' Chrome/' not in self.user_agent:
return 'Safari'
if ' Chrome/' in self.user_agent:
return 'Chrome'
if ' Firefox/' in self.user_agent:
return 'Firefox'
return _('Unknown')
@property
def user_agent_platform(self):
if USER_AGENT_PARSER_SUPPORTED and not getattr(self, 'DISABLE_USER_AGENT_PARSER', False):
family = user_agent_parser.ParseOS(self.user_agent)['family']
return family if family != 'Other' else _('Unknown')
sysinfo = ([''] + self.user_agent.split('(', 1))[-1].split(')', 0)[0]
platforms = [
'Android', 'Linux', 'OpenBSD', 'FreeBSD', 'NetBSD', 'Windows', 'iPhone',
'iPad', 'Macintosh',
]
for platform in platforms:
if platform in sysinfo:
return platform
return _('Unknown')
# Device login provides a convenient and secure way to log into SSO-enabled
# services on a secondary device without entering the user password or
......@@ -55,6 +137,7 @@ from uffd.utils import token_typeable
class DeviceLoginType(enum.Enum):
OAUTH2 = 0
@cleanup_task.delete_by_attribute('expired')
class DeviceLoginInitiation(db.Model):
'''Abstract initiation code class
......@@ -68,17 +151,16 @@ class DeviceLoginInitiation(db.Model):
existing and possibly attacker-controlled code).
An initiation code is securly bound to the session that it was created
with by storing both id and secret in the encrypted and authenticated
session cookie.'''
with by storing both id and secret in the authenticated session cookie.'''
__tablename__ = 'device_login_initiation'
id = Column(Integer(), primary_key=True, autoincrement=True)
type = Column(Enum(DeviceLoginType), nullable=False)
type = Column(Enum(DeviceLoginType, create_constraint=True), nullable=False)
code0 = Column(String(32), unique=True, nullable=False, default=lambda: token_typeable(3))
code1 = Column(String(32), unique=True, nullable=False, default=lambda: token_typeable(3))
secret = Column(String(128), nullable=False, default=lambda: secrets.token_hex(64))
confirmations = relationship('DeviceLoginConfirmation', back_populates='initiation', cascade='all, delete-orphan')
created = Column(DateTime, default=datetime.datetime.now, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
__mapper_args__ = {
'polymorphic_on': type,
......@@ -92,7 +174,9 @@ class DeviceLoginInitiation(db.Model):
@hybrid_property
def expired(self):
return self.created < datetime.datetime.now() - datetime.timedelta(minutes=30)
if self.created is None:
return False
return self.created < datetime.datetime.utcnow() - datetime.timedelta(minutes=30)
@property
def description(self):
......@@ -103,7 +187,7 @@ class DeviceLoginConfirmation(db.Model):
A confirmation code is generated and displayed when an authenticated user
enters an initiation code and confirms the device login attempt. Every
instance is bound to both an initiation code and a user.
instance is bound to both an initiation code and a login session.
The code attribute is formed out of two indepentently unique parts
to ensure that at any time all existing codes differ in at least two
......@@ -116,8 +200,8 @@ class DeviceLoginConfirmation(db.Model):
name='fk_device_login_confirmation_initiation_id_',
onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
initiation = relationship('DeviceLoginInitiation', back_populates='confirmations')
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False, unique=True)
user = relationship('User')
session_id = Column(Integer(), ForeignKey('session.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False, unique=True)
session = relationship('Session')
code0 = Column(String(32), nullable=False, default=lambda: token_typeable(1))
code1 = Column(String(32), nullable=False, default=lambda: token_typeable(1))
......
import datetime
from crypt import crypt
from flask_babel import gettext as _
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.hybrid import hybrid_property
from uffd.database import db
from uffd.user.models import User
from uffd.tasks import cleanup_task
from uffd.utils import token_urlfriendly
from uffd.password_hash import PasswordHashAttribute, LowEntropyPasswordHash
from uffd.database import db
from .user import User
@cleanup_task.delete_by_attribute('expired_and_not_completed')
class Signup(db.Model):
'''Model that represents a self-signup request
......@@ -22,16 +27,17 @@ class Signup(db.Model):
address does not allow a third party to complete the signup procedure and
set a new password with the (also mail-based) password reset functionality.
As long as they are not completed, signup requests have no effect each other
or different parts of the application.'''
As long as they are not completed, signup requests have no effect on each
other or different parts of the application.'''
__tablename__ = 'signup'
id = Column(Integer(), primary_key=True, autoincrement=True)
token = Column(String(128), default=token_urlfriendly, nullable=False)
created = Column(DateTime, default=datetime.datetime.now, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
loginname = Column(Text)
displayname = Column(Text)
mail = Column(Text)
pwhash = Column(Text)
_password = Column('pwhash', Text)
password = PasswordHashAttribute('_password', LowEntropyPasswordHash)
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=True, unique=True)
user = relationship('User', backref=backref('signups', cascade='all, delete-orphan'))
......@@ -41,23 +47,26 @@ class Signup(db.Model):
'polymorphic_on': type
}
# Write-only property
def password(self, value):
def set_password(self, value):
if not User().set_password(value):
return
self.pwhash = crypt(value)
password = property(fset=password)
return False
self.password = value
return True
def check_password(self, value):
return self.pwhash is not None and crypt(value, self.pwhash) == self.pwhash
@property
@hybrid_property
def expired(self):
return self.created is not None and datetime.datetime.now() >= self.created + datetime.timedelta(hours=48)
if self.created is None:
return False
return self.created < datetime.datetime.utcnow() - datetime.timedelta(hours=48)
@property
@hybrid_property
def completed(self):
return self.user is not None
# pylint: disable=singleton-comparison
return self.user_id != None
@hybrid_property
def expired_and_not_completed(self):
return db.and_(self.expired, db.not_(self.completed))
def validate(self): # pylint: disable=too-many-return-statements
'''Return whether the signup request is valid and Signup.finish is likely to succeed
......@@ -66,18 +75,18 @@ class Signup(db.Model):
is False and `errmsg` contains a string describing why. Otherwise
`valid` is True.'''
if self.completed or self.expired:
return False, 'Invalid signup request'
return False, _('Invalid signup request')
if not User().set_loginname(self.loginname):
return False, 'Login name is invalid'
return False, _('Login name is invalid')
if not User().set_displayname(self.displayname):
return False, 'Display name is invalid'
if not User().set_mail(self.mail):
return False, 'Mail address is invalid'
if self.pwhash is None:
return False, 'Invalid password'
return False, _('Display name is invalid')
if not User().set_primary_email_address(self.mail):
return False, _('E-Mail address is invalid')
if not self.password:
return False, _('Invalid password')
if User.query.filter_by(loginname=self.loginname).all():
return False, 'A user with this login name already exists'
return True, 'Valid'
return False, _('A user with this login name already exists')
return True, _('Valid')
def finish(self, password):
'''Complete the signup procedure and return the new user
......@@ -91,17 +100,23 @@ class Signup(db.Model):
`errmsg` contains a string describing why. Otherwise `user` is a
User object.'''
if self.completed or self.expired:
return None, 'Invalid signup request'
if not self.check_password(password):
return None, 'Wrong password'
return None, _('Invalid signup request')
if not self.password.verify(password):
return None, _('Wrong password')
if User.query.filter_by(loginname=self.loginname).all():
return None, 'A user with this login name already exists'
user = User(loginname=self.loginname, displayname=self.displayname, mail=self.mail, password=password)
return None, _('A user with this login name already exists')
# Flush to make sure the flush below does not catch unrelated errors
db.session.flush()
user = User(loginname=self.loginname, displayname=self.displayname, primary_email_address=self.mail, password=self.password)
db.session.add(user)
try:
db.session.flush()
except IntegrityError:
return None, _('Login name or e-mail address is already in use')
user.update_groups() # pylint: disable=no-member
self.user = user
self.loginname = None
self.displayname = None
self.mail = None
self.pwhash = None
return user, 'Success'
self.password = None
return user, _('Success')
import string
import re
import datetime
import unicodedata
from flask import current_app, escape
from flask_babel import lazy_gettext
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Text, DateTime
from sqlalchemy.orm import relationship, validates
from sqlalchemy.ext.hybrid import hybrid_property
from uffd.database import db
from uffd.remailer import remailer
from uffd.utils import token_urlfriendly
from uffd.password_hash import PasswordHashAttribute, LowEntropyPasswordHash, HighEntropyPasswordHash
from .misc import FeatureFlag, Lock
class IDRangeExhaustedError(Exception):
pass
class IDAlreadyAllocatedError(ValueError):
pass
# Helper class for UID/GID allocation that prevents reuse even if
# users/groups are deleted.
#
# To keep track of formerly used UIDs/GIDs, they are always also added to
# uid/gid allocation tables. Rows in these tables are never deleted.
# User/group tables have foreign key constraints to ensure that there can
# only ever be three cases for a given ID:
#
# 1. The ID was never used (does not exist in either user/group or allocation
# table)
# 2. The ID was used, but the user/group was deleted (it does not exist in
# user/group table, but it exists in the allocation table)
# 3. The ID is in use (it exists in both the user/group and the allocation
# table)
#
# For auto-allocation, there are a few edge cases to consider:
#
# 1. GIDs can be chosen freely in the web interface, e.g. one could easily
# create a group with the last GID in range.
# 2. For UIDs there are two ranges (for regular users and for service users).
# The ranges may either be the same or they may be different but
# non-overlapping.
# 3. ID ranges can be changed (e.g. extended to either side if the old range
# is exhausted). Existing IDs should not change.
#
# The approach we use here is to always auto-allocate the first unused id
# in range. This approach handles the three edge cases well and even behaves
# sanely in unsupported configurations like different but overlapping UID
# ranges.
class IDAllocator:
# pylint completely fails to understand SQLAlchemy's query functions
# pylint: disable=no-member
def __init__(self, name):
self.name = name
self.lock = Lock(f'{name}_allocation')
self.allocation_table = db.Table(f'{name}_allocation', db.Column('id', db.Integer(), primary_key=True))
def allocate(self, id):
self.lock.acquire()
result = db.session.execute(
db.select([self.allocation_table.c.id])
.where(self.allocation_table.c.id == id)
).scalar()
if result is not None:
raise IDAlreadyAllocatedError(f'Cannot allocate {self.name}: {id} is in use or was used in the past')
db.session.execute(db.insert(self.allocation_table).values(id=id))
def auto(self, min_id, max_id):
'''Auto-allocate and return an unused id in range'''
self.lock.acquire()
# We cannot easily iterate through a large range of numbers with generic
# SQL statements looking for unused ids. So to find the first unused id in
# range, we look for the first used id in range that is followed by an
# unused id. This does not work if there are no used ids in range (returns
# NULL) or if min_id is unused (returns higher id while it should return
# min_id). To fix this we also check if min_id is used or not.
tmp = db.aliased(self.allocation_table)
first_unused_id = db.session.execute(
db.select([db.func.min(self.allocation_table.c.id + 1)])
.where(self.allocation_table.c.id >= min_id)
.where(db.not_(db.exists().where(tmp.c.id == self.allocation_table.c.id + 1)))
).scalar()
min_id_used = db.session.execute(
db.select([db.exists()
.where(self.allocation_table.c.id == min_id)])
).scalar()
if not min_id_used:
first_unused_id = min_id
if first_unused_id > max_id:
raise IDRangeExhaustedError(f'Cannot auto-allocate {self.name}: Range is exhausted')
db.session.execute(db.insert(self.allocation_table).values(id=first_unused_id))
return first_unused_id
def user_unix_uid_default(context):
if context.get_current_parameters()['is_service_user']:
min_uid = current_app.config['USER_SERVICE_MIN_UID']
max_uid = current_app.config['USER_SERVICE_MAX_UID']
else:
min_uid = current_app.config['USER_MIN_UID']
max_uid = current_app.config['USER_MAX_UID']
return User.unix_uid_allocator.auto(min_uid, max_uid)
class User(db.Model):
# Allows 8 to 256 ASCII letters (lower and upper case), digits, spaces and
# symbols/punctuation characters. It disallows control characters and
# non-ASCII characters to prevent setting passwords considered invalid by
# SASLprep.
#
# This REGEX ist used both in Python and JS.
PASSWORD_REGEX = '[ -~]*'
PASSWORD_MINLEN = 8
PASSWORD_MAXLEN = 256
PASSWORD_DESCRIPTION = lazy_gettext('At least %(minlen)d and at most %(maxlen)d characters. ' + \
'Only letters, digits, spaces and some symbols (<code>%(symbols)s</code>) allowed. ' + \
'Please use a password manager.',
minlen=PASSWORD_MINLEN, maxlen=PASSWORD_MAXLEN, symbols=escape('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'))
__tablename__ = 'user'
id = Column(Integer(), primary_key=True, autoincrement=True)
unix_uid_allocator = IDAllocator('uid')
unix_uid = Column(Integer(), ForeignKey('uid_allocation.id'), unique=True, nullable=False, default=user_unix_uid_default)
@validates('unix_uid')
def validate_unix_uid(self, key, value): # pylint: disable=unused-argument
if self.unix_uid != value and value is not None:
self.unix_uid_allocator.allocate(value)
return value
loginname = Column(String(32), unique=True, nullable=False)
displayname = Column(String(128), nullable=False)
all_emails = relationship(
'UserEmail',
foreign_keys='UserEmail.user_id',
cascade='all, delete-orphan',
back_populates='user',
post_update=True,
)
verified_emails = relationship(
'UserEmail',
foreign_keys='UserEmail.user_id',
viewonly=True,
primaryjoin='and_(User.id == UserEmail.user_id, UserEmail.verified)',
)
primary_email_id = Column(Integer(), ForeignKey('user_email.id', onupdate='CASCADE'), nullable=False)
primary_email = relationship('UserEmail', foreign_keys='User.primary_email_id')
# recovery_email_id == NULL -> use primary email
recovery_email_id = Column(Integer(), ForeignKey('user_email.id', onupdate='CASCADE', ondelete='SET NULL'))
recovery_email = relationship('UserEmail', foreign_keys='User.recovery_email_id')
@validates('primary_email', 'recovery_email')
def validate_email(self, key, value):
if value is not None:
if not value.user:
value.user = self
if value.user != self:
raise ValueError(f'UserEmail assigned to User.{key} is not associated with user')
if not value.verified:
raise ValueError(f'UserEmail assigned to User.{key} is not verified')
return value
_password = Column('pwhash', Text(), nullable=True)
password = PasswordHashAttribute('_password', LowEntropyPasswordHash)
is_service_user = Column(Boolean(create_constraint=True), default=False, nullable=False)
is_deactivated = Column(Boolean(create_constraint=True), default=False, nullable=False)
groups = relationship('Group', secondary='user_groups', back_populates='members')
roles = relationship('Role', secondary='role_members', back_populates='members')
service_users = relationship('ServiceUser', viewonly=True)
sessions = relationship('Session', back_populates='user', cascade='all, delete-orphan')
def __init__(self, primary_email_address=None, **kwargs):
super().__init__(**kwargs)
if primary_email_address is not None:
self.primary_email = UserEmail(address=primary_email_address, verified=True)
@property
def unix_gid(self):
return current_app.config['USER_GID']
def is_in_group(self, name):
if not name:
return True
for group in self.groups:
if group.name == name:
return True
return False
def has_permission(self, required_group=None):
if not required_group:
return True
group_names = {group.name for group in self.groups}
group_sets = required_group
if isinstance(group_sets, str):
group_sets = [group_sets]
for group_set in group_sets:
if isinstance(group_set, str):
group_set = [group_set]
if set(group_set) - group_names == set():
return True
return False
def set_loginname(self, value, ignore_blocklist=False):
if len(value) > 32 or len(value) < 1:
return False
for char in value:
if not char in string.ascii_lowercase + string.digits + '_-':
return False
if not ignore_blocklist:
for expr in current_app.config['LOGINNAME_BLOCKLIST']:
if re.match(expr, value):
return False
self.loginname = value
return True
def set_displayname(self, value):
if len(value) > 128 or len(value) < 1:
return False
self.displayname = value
return True
def set_password(self, value):
if len(value) < self.PASSWORD_MINLEN or len(value) > self.PASSWORD_MAXLEN or not re.fullmatch(self.PASSWORD_REGEX, value):
return False
self.password = value
return True
def set_primary_email_address(self, address):
# UserEmail.query.filter_by(user=self, address=address).first() would cause
# a flush, so we do this in python. A flush would cause an IntegrityError if
# this method is used a new User object, since primary_email_id is not
# nullable.
email = ([item for item in self.all_emails if item.address == address] or [None])[0]
if not email:
email = UserEmail()
if not email.set_address(address):
return False
email.verified = True
self.primary_email = email
return True
# Somehow pylint non-deterministically fails to detect that .update_groups is set in role.models
def update_groups(self):
pass
class UserEmail(db.Model):
__tablename__ = 'user_email'
id = Column(Integer(), primary_key=True, autoincrement=True)
# We have a cyclic dependency between User.primary_email and UserEmail.user.
# To solve this, we make UserEmail.user nullable, add validators, and set
# post_update=True here and for the backref.
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE', use_alter=True))
user = relationship('User', foreign_keys='UserEmail.user_id', back_populates='all_emails', post_update=True)
@validates('user')
def validate_user(self, key, value): # pylint: disable=unused-argument
if self.user is not None and self.user != value:
raise ValueError('UserEmail.user cannot be changed once set')
return value
@classmethod
def normalize_address(cls, value):
return unicodedata.normalize('NFKC', value).lower().strip()
address = Column(String(128), nullable=False)
address_normalized = Column(String(128), nullable=False)
@validates('address')
def validate_address(self, key, value): # pylint: disable=unused-argument
if self.address is not None and self.address != value:
raise ValueError('UserEmail.address cannot be changed once set')
self.address_normalized = self.normalize_address(value)
return value
# True or None/NULL (not False, see constraints below)
_verified = Column('verified', Boolean(create_constraint=True), nullable=True)
@hybrid_property
def verified(self):
# pylint: disable=singleton-comparison
return self._verified != None
@verified.setter
def verified(self, value):
if self._verified and not value:
raise ValueError('UserEmail cannot be unverified once verified')
self._verified = True if value else None
verification_legacy_id = Column(Integer()) # id of old MailToken
_verification_secret = Column('verification_secret', Text())
verification_secret = PasswordHashAttribute('_verification_secret', HighEntropyPasswordHash)
verification_expires = Column(DateTime)
# Until uffd v3, we make the stricter unique constraints optional, by having
# enable_strict_constraints act as a switch to enable/disable the constraints
# on a per-row basis.
# True or None/NULL if disabled (not False, see constraints below)
enable_strict_constraints = Column(
Boolean(create_constraint=True),
nullable=True,
default=db.select([db.case([(FeatureFlag.unique_email_addresses.expr, True)], else_=None)])
)
# The unique constraints rely on the common interpretation of SQL92, that if
# any column in a unique constraint is NULL, the unique constraint essentially
# does not apply to the row. This is how SQLite, MySQL/MariaDB, PostgreSQL and
# other common databases behave. A few others like Microsoft SQL Server do not
# follow this, but we don't support them anyway.
__table_args__ = (
# A user cannot have the same address more than once, regardless of verification status
db.UniqueConstraint('user_id', 'address', name='uq_user_email_user_id_address'), # Legacy, to be removed in v3
# Same unique constraint as uq_user_email_user_id_address, but with
# address_normalized instead of address. Only active if
# enable_strict_constraints is not NULL.
db.UniqueConstraint('user_id', 'address_normalized', 'enable_strict_constraints',
name='uq_user_email_user_id_address_normalized'),
# The same verified address can only exist once. Only active if
# enable_strict_constraints is not NULL. Unverified addresses are ignored,
# since verified is NULL in that case.
db.UniqueConstraint('address_normalized', 'verified', 'enable_strict_constraints',
name='uq_user_email_address_normalized_verified'),
)
def set_address(self, value):
if len(value) < 3 or '@' not in value:
return False
domain = value.rsplit('@', 1)[-1]
if remailer.is_remailer_domain(domain):
return False
self.address = value
return True
def start_verification(self):
if self.verified:
raise Exception('UserEmail.start_verification must not be called if address is already verified')
self.verification_legacy_id = None
secret = token_urlfriendly()
self.verification_secret = secret
self.verification_expires = datetime.datetime.utcnow() + datetime.timedelta(days=2)
return secret
@hybrid_property
def verification_expired(self):
if self.verification_expires is None:
return True
return self.verification_expires < datetime.datetime.utcnow()
def finish_verification(self, secret):
# pylint: disable=using-constant-test,no-member
if self.verification_expired:
return False
if not self.verification_secret.verify(secret):
return False
self.verification_legacy_id = None
self.verification_secret = None
self.verification_expires = None
self.verified = True
return True
@FeatureFlag.unique_email_addresses.enable_hook
def enable_unique_email_addresses():
UserEmail.query.update({UserEmail.enable_strict_constraints: True})
@FeatureFlag.unique_email_addresses.disable_hook
def disable_unique_email_addresses():
UserEmail.query.update({UserEmail.enable_strict_constraints: None})
# pylint: disable=E1101
user_groups = db.Table('user_groups',
Column('user_id', Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True),
Column('group_id', Integer(), ForeignKey('group.id', onupdate='CASCADE', ondelete='CASCADE'), primary_key=True)
)
def group_unix_gid_default():
return Group.unix_gid_allocator.auto(current_app.config['GROUP_MIN_GID'], current_app.config['GROUP_MAX_GID'])
class Group(db.Model):
__tablename__ = 'group'
id = Column(Integer(), primary_key=True, autoincrement=True)
unix_gid_allocator = IDAllocator('gid')
unix_gid = Column(Integer(), ForeignKey('gid_allocation.id'), unique=True, nullable=False, default=group_unix_gid_default)
@validates('unix_gid')
def validate_unix_gid(self, key, value): # pylint: disable=unused-argument
if self.unix_gid != value and value is not None:
self.unix_gid_allocator.allocate(value)
return value
name = Column(String(32), unique=True, nullable=False)
description = Column(String(128), nullable=False, default='')
members = relationship('User', secondary='user_groups', back_populates='groups')
def set_name(self, value):
if len(value) > 32 or len(value) < 1:
return False
for char in value:
if not char in string.ascii_lowercase + string.digits + '_-':
return False
self.name = value
return True
def setup_navbar(app):
def setup_navbar(app, positions):
app.navbarPositions = positions
app.navbarList = []
app.jinja_env.globals['getnavbar'] = lambda: [n for n in app.navbarList if n['visible']()]
......@@ -8,7 +9,7 @@ def setup_navbar(app):
# ( see: http://fontawesome.io/icons/ )
# visible is a function that returns "True" if this icon should be visible in the calling context
# pylint: disable=too-many-arguments
def register_navbar(position, name, iconlib='fa', icon=None, group=None, endpoint=None, blueprint=None, visible=None):
def register_navbar(name, iconlib='fa', icon=None, group=None, endpoint=None, blueprint=None, visible=None):
def wrapper(func):
def deferred_call(state):
assert blueprint
......@@ -28,7 +29,11 @@ def register_navbar(position, name, iconlib='fa', icon=None, group=None, endpoin
item['name'] = name
item['blueprint'] = blueprint
item['visible'] = visible or (lambda: True)
item['position'] = position
item['position'] = 99
if blueprint.name in state.app.navbarPositions:
item['position'] = state.app.navbarPositions.index(blueprint.name)
else:
item['visible'] = lambda: False
state.app.navbarList.append(item)
state.app.navbarList.sort(key=lambda item: item['position'])
blueprint.record_once(deferred_call)
......
from .views import bp as _bp
bp = [_bp]
from flask import current_app
from flask_babel import get_locale, gettext as _
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey
from sqlalchemy.orm import relationship
from uffd.database import db
from uffd.session.models import DeviceLoginInitiation, DeviceLoginType
class OAuth2Client:
def __init__(self, client_id, client_secret, redirect_uris, required_group=None, logout_urls=None, **kwargs):
self.client_id = client_id
self.client_secret = client_secret
# We only support the Authorization Code Flow for confidential (server-side) clients
self.client_type = 'confidential'
self.redirect_uris = redirect_uris
self.default_scopes = ['profile']
self.required_group = required_group
self.logout_urls = []
for url in (logout_urls or []):
if isinstance(url, str):
self.logout_urls.append(['GET', url])
else:
self.logout_urls.append(url)
self.kwargs = kwargs
@property
def login_message(self):
return self.kwargs.get('login_message_' + get_locale().language,
self.kwargs.pop('login_message', _('You need to login to access this service')))
@classmethod
def from_id(cls, client_id):
return OAuth2Client(client_id, **current_app.config['OAUTH2_CLIENTS'][client_id])
@property
def default_redirect_uri(self):
return self.redirect_uris[0]
def access_allowed(self, user):
return user.has_permission(self.required_group)
class OAuth2Grant(db.Model):
__tablename__ = 'oauth2grant'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
user = relationship('User')
client_id = Column(String(40), nullable=False)
@property
def client(self):
return OAuth2Client.from_id(self.client_id)
@client.setter
def client(self, newclient):
self.client_id = newclient.client_id
code = Column(String(255), index=True, nullable=False)
redirect_uri = Column(String(255), nullable=False)
expires = Column(DateTime, nullable=False)
_scopes = Column(Text, nullable=False, default='')
@property
def scopes(self):
if self._scopes:
return self._scopes.split()
return []
def delete(self):
db.session.delete(self)
db.session.commit()
return self
class OAuth2Token(db.Model):
__tablename__ = 'oauth2token'
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
user = relationship('User')
client_id = Column(String(40), nullable=False)
@property
def client(self):
return OAuth2Client.from_id(self.client_id)
@client.setter
def client(self, newclient):
self.client_id = newclient.client_id
# currently only bearer is supported
token_type = Column(String(40), nullable=False)
access_token = Column(String(255), unique=True, nullable=False)
refresh_token = Column(String(255), unique=True, nullable=False)
expires = Column(DateTime, nullable=False)
_scopes = Column(Text, nullable=False, default='')
@property
def scopes(self):
if self._scopes:
return self._scopes.split()
return []
def delete(self):
db.session.delete(self)
db.session.commit()
return self
class OAuth2DeviceLoginInitiation(DeviceLoginInitiation):
__mapper_args__ = {
'polymorphic_identity': DeviceLoginType.OAUTH2
}
oauth2_client_id = Column(String(40))
@property
def oauth2_client(self):
return OAuth2Client.from_id(self.oauth2_client_id)
@property
def description(self):
return self.oauth2_client.client_id
import datetime
import functools
import secrets
import urllib.parse
from flask import Blueprint, request, jsonify, render_template, session, redirect, url_for, flash, abort
import oauthlib.oauth2
from flask_babel import gettext as _
from sqlalchemy.exc import IntegrityError
from uffd.ratelimit import host_ratelimit, format_delay
from uffd.database import db
from uffd.secure_redirect import secure_local_redirect
from uffd.session.models import DeviceLoginConfirmation
from .models import OAuth2Client, OAuth2Grant, OAuth2Token, OAuth2DeviceLoginInitiation
class UffdRequestValidator(oauthlib.oauth2.RequestValidator):
# Argument "oauthreq" is named "request" in superclass but this clashes with flask's "request" object
# Arguments "token_value" and "token_data" are named "token" in superclass but this clashs with "token" endpoint
# pylint: disable=arguments-differ,arguments-renamed,unused-argument,too-many-public-methods,abstract-method
# In all cases (aside from validate_bearer_token), either validate_client_id or authenticate_client is called
# before anything else. authenticate_client_id would be called instead of authenticate_client for non-confidential
# clients. However, we don't support those.
def validate_client_id(self, client_id, oauthreq, *args, **kwargs):
try:
oauthreq.client = OAuth2Client.from_id(client_id)
return True
except KeyError:
return False
def authenticate_client(self, oauthreq, *args, **kwargs):
authorization = oauthreq.extra_credentials.get('authorization')
if authorization:
# From RFC6749 2.3.1:
# Clients in possession of a client password MAY use the HTTP Basic authentication
# scheme as defined in [RFC2617] to authenticate with the authorization server.
# The client identifier is encoded using the "application/x-www-form-urlencoded"
# encoding algorithm per Appendix B, and the encoded value is used as the username
# the client password is encoded using the same algorithm and used as the password.
oauthreq.client_id = urllib.parse.unquote(authorization.username)
oauthreq.client_secret = urllib.parse.unquote(authorization.password)
if oauthreq.client_secret is None:
return False
try:
oauthreq.client = OAuth2Client.from_id(oauthreq.client_id)
except KeyError:
return False
return secrets.compare_digest(oauthreq.client.client_secret, oauthreq.client_secret)
def get_default_redirect_uri(self, client_id, oauthreq, *args, **kwargs):
return oauthreq.client.default_redirect_uri
def validate_redirect_uri(self, client_id, redirect_uri, oauthreq, *args, **kwargs):
return redirect_uri in oauthreq.client.redirect_uris
def validate_response_type(self, client_id, response_type, client, oauthreq, *args, **kwargs):
return response_type == 'code'
def get_default_scopes(self, client_id, oauthreq, *args, **kwargs):
return oauthreq.client.default_scopes
def validate_scopes(self, client_id, scopes, client, oauthreq, *args, **kwargs):
if scopes == ['']:
oauthreq.scopes = scopes = self.get_default_scopes(client_id, oauthreq)
return set(scopes).issubset({'profile'})
def save_authorization_code(self, client_id, code, oauthreq, *args, **kwargs):
expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
grant = OAuth2Grant(user=oauthreq.user, client_id=client_id, code=code['code'],
redirect_uri=oauthreq.redirect_uri, expires=expires, _scopes=' '.join(oauthreq.scopes))
db.session.add(grant)
db.session.commit()
# Oauthlib does not really provide a way to customize grant code generation.
# Actually `code` is created just before `save_authorization_code` is called
# and the same dict is later used to generate the OAuth2 response. So by
# modifing the `code` dict we can actually influence the grant code.
code['code'] = f"{grant.id}-{code['code']}"
def validate_code(self, client_id, code, client, oauthreq, *args, **kwargs):
if '-' not in code:
return False
grant_id, grant_code = code.split('-', 2)
oauthreq.grant = OAuth2Grant.query.get(grant_id)
if not oauthreq.grant or oauthreq.grant.client_id != client_id:
return False
if not secrets.compare_digest(oauthreq.grant.code, grant_code):
return False
if datetime.datetime.utcnow() > oauthreq.grant.expires:
return False
oauthreq.user = oauthreq.grant.user
oauthreq.scopes = oauthreq.grant.scopes
return True
def invalidate_authorization_code(self, client_id, code, oauthreq, *args, **kwargs):
OAuth2Grant.query.filter_by(client_id=client_id, code=code).delete()
db.session.commit()
def save_bearer_token(self, token_data, oauthreq, *args, **kwargs):
OAuth2Token.query.filter_by(client_id=oauthreq.client.client_id, user=oauthreq.user).delete()
expires_in = token_data.get('expires_in')
expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=expires_in)
tok = OAuth2Token(
user=oauthreq.user,
client_id=oauthreq.client.client_id,
token_type=token_data['token_type'],
access_token=token_data['access_token'],
refresh_token=token_data['refresh_token'],
expires=expires,
_scopes=' '.join(oauthreq.scopes)
)
db.session.add(tok)
db.session.commit()
# Oauthlib does not really provide a way to customize access/refresh token
# generation. Actually `token_data` is created just before
# `save_bearer_token` is called and the same dict is later used to generate
# the OAuth2 response. So by modifing the `token_data` dict we can actually
# influence the tokens.
token_data['access_token'] = f"{tok.id}-{token_data['access_token']}"
token_data['refresh_token'] = f"{tok.id}-{token_data['refresh_token']}"
return oauthreq.client.default_redirect_uri
def validate_grant_type(self, client_id, grant_type, client, oauthreq, *args, **kwargs):
return grant_type == 'authorization_code'
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, oauthreq, *args, **kwargs):
return redirect_uri == oauthreq.grant.redirect_uri
def validate_bearer_token(self, token_value, scopes, oauthreq):
if '-' not in token_value:
return False
tok_id, tok_secret = token_value.split('-', 2)
tok = OAuth2Token.query.get(tok_id)
if not tok or not secrets.compare_digest(tok.access_token, tok_secret):
return False
if datetime.datetime.utcnow() > tok.expires:
oauthreq.error_message = 'Token expired'
return False
if not set(scopes).issubset(tok.scopes):
oauthreq.error_message = 'Scopes invalid'
return False
oauthreq.access_token = tok
oauthreq.user = tok.user
oauthreq.scopes = scopes
oauthreq.client = tok.client
oauthreq.client_id = tok.client_id
return True
# get_original_scopes/validate_refresh_token are only used for refreshing tokens. We don't implement the refresh endpoint.
# revoke_token is only used for revoking access tokens. We don't implement the revoke endpoint.
# get_id_token/validate_silent_authorization/validate_silent_login are OpenID Connect specfic.
# validate_user/validate_user_match are not required for Authorization Code Grant flow.
validator = UffdRequestValidator()
server = oauthlib.oauth2.WebApplicationServer(validator)
bp = Blueprint('oauth2', __name__, url_prefix='/oauth2/', template_folder='templates')
@bp.errorhandler(oauthlib.oauth2.rfc6749.errors.OAuth2Error)
def handle_oauth2error(error):
return render_template('oauth2/error.html', error=type(error).__name__, error_description=error.description), 400
@bp.route('/authorize', methods=['GET', 'POST'])
def authorize():
scopes, credentials = server.validate_authorization_request(request.url, request.method, request.form, request.headers)
client = OAuth2Client.from_id(credentials['client_id'])
if request.user:
credentials['user'] = request.user
elif 'devicelogin_started' in session:
del session['devicelogin_started']
host_delay = host_ratelimit.get_delay()
if host_delay:
flash(_('We received too many requests from your ip address/network! Please wait at least %(delay)s.', delay=format_delay(host_delay)))
return redirect(url_for('session.login', ref=request.full_path, devicelogin=True))
host_ratelimit.log()
initiation = OAuth2DeviceLoginInitiation(oauth2_client_id=client.client_id)
db.session.add(initiation)
try:
db.session.commit()
except IntegrityError:
flash(_('Device login is currently not available. Try again later!'))
return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True))
session['devicelogin_id'] = initiation.id
session['devicelogin_secret'] = initiation.secret
return redirect(url_for('session.devicelogin', ref=request.full_path))
elif 'devicelogin_id' in session and 'devicelogin_secret' in session and 'devicelogin_confirmation' in session:
initiation = OAuth2DeviceLoginInitiation.query.filter_by(id=session['devicelogin_id'], secret=session['devicelogin_secret'],
oauth2_client_id=client.client_id).one_or_none()
confirmation = DeviceLoginConfirmation.query.get(session['devicelogin_confirmation'])
del session['devicelogin_id']
del session['devicelogin_secret']
del session['devicelogin_confirmation']
if not initiation or initiation.expired or not confirmation:
flash(_('Device login failed'))
return redirect(url_for('session.login', ref=request.full_path, devicelogin=True))
credentials['user'] = confirmation.user
db.session.delete(initiation)
db.session.commit()
else:
flash(client.login_message)
return redirect(url_for('session.login', ref=request.full_path, devicelogin=True))
# Here we would normally ask the user, if he wants to give the requesting
# service access to his data. Since we only have trusted services (the
# clients defined in the server config), we don't ask for consent.
if not client.access_allowed(credentials['user']):
abort(403, description=_("You don't have the permission to access the service <b>%(service_name)s</b>.", service_name=client.client_id))
session['oauth2-clients'] = session.get('oauth2-clients', [])
if client.client_id not in session['oauth2-clients']:
session['oauth2-clients'].append(client.client_id)
headers, body, status = server.create_authorization_response(request.url, request.method, request.form, request.headers, scopes, credentials)
return body or '', status, headers
@bp.route('/token', methods=['GET', 'POST'])
def token():
headers, body, status = server.create_token_response(request.url, request.method, request.form,
request.headers, {'authorization': request.authorization})
return body, status, headers
def oauth_required(*scopes):
def wrapper(func):
@functools.wraps(func)
def decorator(*args, **kwargs):
valid, oauthreq = server.verify_request(request.url, request.method, request.form, request.headers, scopes)
if not valid:
abort(401)
request.oauth = oauthreq
return func(*args, **kwargs)
return decorator
return wrapper
@bp.route('/userinfo')
@oauth_required('profile')
def userinfo():
user = request.oauth.user
return jsonify(
id=user.unix_uid,
name=user.displayname,
nickname=user.loginname,
email=user.mail,
groups=[group.name for group in user.groups]
)
@bp.app_url_defaults
def inject_logout_params(endpoint, values):
if endpoint != 'oauth2.logout' or not session.get('oauth2-clients'):
return
values['client_ids'] = ','.join(session['oauth2-clients'])
@bp.route('/logout')
def logout():
if not request.values.get('client_ids'):
return secure_local_redirect(request.values.get('ref', '/'))
client_ids = request.values['client_ids'].split(',')
clients = [OAuth2Client.from_id(client_id) for client_id in client_ids]
return render_template('oauth2/logout.html', clients=clients)
import secrets
import hashlib
import base64
from crypt import crypt # pylint: disable=deprecated-module
import argon2
def build_value(method_name, data):
return '{' + method_name + '}' + data
def parse_value(value):
if value is not None and value.startswith('{') and '}' in value:
method_name, data = value[1:].split('}', 1)
return method_name.lower(), data
raise ValueError('Invalid password hash')
class PasswordHashRegistry:
'''Factory for creating objects of the correct PasswordHash subclass for a
given password hash value'''
def __init__(self):
self.methods = {}
def register(self, cls):
assert cls.METHOD_NAME not in self.methods
self.methods[cls.METHOD_NAME] = cls
return cls
def parse(self, value, **kwargs):
method_name, _ = parse_value(value)
method_cls = self.methods.get(method_name)
if method_cls is None:
raise ValueError(f'Unknown password hash method {method_name}')
return method_cls(value, **kwargs)
registry = PasswordHashRegistry()
class PasswordHash:
'''OpenLDAP-/NIS-style password hash
Instances wrap password hash strings in the form "{METHOD_NAME}DATA".
Allows gradual migration of password hashing methods by checking
PasswordHash.needs_rehash every time the password is processed and rehashing
it with PasswordHash.from_password if needed. For PasswordHash.needs_rehash
to work, the PasswordHash subclass for the current password hashing method
is instantiated with target_cls set to the PasswordHash subclass of the
intended hashing method.
Instances should be created with PasswordHashRegistry.parse to get the
appropriate subclass based on the method name in a value.'''
METHOD_NAME = None
def __init__(self, value, target_cls=None):
method_name, data = parse_value(value)
if method_name != self.METHOD_NAME:
raise ValueError('Invalid password hash')
self.value = value
self.data = data
self.target_cls = target_cls or type(self)
@classmethod
def from_password(cls, password):
raise NotImplementedError()
def verify(self, password):
raise NotImplementedError()
@property
def needs_rehash(self):
return not isinstance(self, self.target_cls)
@registry.register
class PlaintextPasswordHash(PasswordHash):
'''Pseudo password hash for passwords stored without hashing
Should only be used for migration of existing plaintext passwords. Add the
prefix "{plain}" for this.'''
METHOD_NAME = 'plain'
@classmethod
def from_password(cls, password):
return cls(build_value(cls.METHOD_NAME, password))
def verify(self, password):
return secrets.compare_digest(self.data, password)
class HashlibPasswordHash(PasswordHash):
HASHLIB_ALGORITHM = None
@classmethod
def from_password(cls, password):
ctx = hashlib.new(cls.HASHLIB_ALGORITHM, password.encode())
return cls(build_value(cls.METHOD_NAME, base64.b64encode(ctx.digest()).decode()))
def verify(self, password):
digest = base64.b64decode(self.data.encode())
ctx = hashlib.new(self.HASHLIB_ALGORITHM, password.encode())
return secrets.compare_digest(digest, ctx.digest())
class SaltedHashlibPasswordHash(PasswordHash):
HASHLIB_ALGORITHM = None
@classmethod
def from_password(cls, password):
salt = secrets.token_bytes(8)
ctx = hashlib.new(cls.HASHLIB_ALGORITHM)
ctx.update(password.encode())
ctx.update(salt)
return cls(build_value(cls.METHOD_NAME, base64.b64encode(ctx.digest()+salt).decode()))
def verify(self, password):
data = base64.b64decode(self.data.encode())
ctx = hashlib.new(self.HASHLIB_ALGORITHM)
digest = data[:ctx.digest_size]
salt = data[ctx.digest_size:]
ctx.update(password.encode())
ctx.update(salt)
return secrets.compare_digest(digest, ctx.digest())
@registry.register
class MD5PasswordHash(HashlibPasswordHash):
METHOD_NAME = 'md5'
HASHLIB_ALGORITHM = 'md5'
@registry.register
class SaltedMD5PasswordHash(SaltedHashlibPasswordHash):
METHOD_NAME = 'smd5'
HASHLIB_ALGORITHM = 'md5'
@registry.register
class SHA1PasswordHash(HashlibPasswordHash):
METHOD_NAME = 'sha'
HASHLIB_ALGORITHM = 'sha1'
@registry.register
class SaltedSHA1PasswordHash(SaltedHashlibPasswordHash):
METHOD_NAME = 'ssha'
HASHLIB_ALGORITHM = 'sha1'
@registry.register
class SHA256PasswordHash(HashlibPasswordHash):
METHOD_NAME = 'sha256'
HASHLIB_ALGORITHM = 'sha256'
@registry.register
class SaltedSHA256PasswordHash(SaltedHashlibPasswordHash):
METHOD_NAME = 'ssha256'
HASHLIB_ALGORITHM = 'sha256'
@registry.register
class SHA384PasswordHash(HashlibPasswordHash):
METHOD_NAME = 'sha384'
HASHLIB_ALGORITHM = 'sha384'
@registry.register
class SaltedSHA384PasswordHash(SaltedHashlibPasswordHash):
METHOD_NAME = 'ssha384'
HASHLIB_ALGORITHM = 'sha384'
@registry.register
class SHA512PasswordHash(HashlibPasswordHash):
METHOD_NAME = 'sha512'
HASHLIB_ALGORITHM = 'sha512'
@registry.register
class SaltedSHA512PasswordHash(SaltedHashlibPasswordHash):
METHOD_NAME = 'ssha512'
HASHLIB_ALGORITHM = 'sha512'
@registry.register
class CryptPasswordHash(PasswordHash):
METHOD_NAME = 'crypt'
@classmethod
def from_password(cls, password):
return cls(build_value(cls.METHOD_NAME, crypt(password)))
def verify(self, password):
return secrets.compare_digest(crypt(password, self.data), self.data)
@registry.register
class Argon2PasswordHash(PasswordHash):
METHOD_NAME = 'argon2'
hasher = argon2.PasswordHasher()
@classmethod
def from_password(cls, password):
return cls(build_value(cls.METHOD_NAME, cls.hasher.hash(password)))
def verify(self, password):
try:
return self.hasher.verify(self.data, password)
except argon2.exceptions.Argon2Error:
return False
except argon2.exceptions.InvalidHash:
return False
@property
def needs_rehash(self):
return super().needs_rehash or self.hasher.check_needs_rehash(self.data)
class InvalidPasswordHash:
def __init__(self, value=None):
self.value = value
# pylint: disable=unused-argument
def verify(self, password):
return False
@property
def needs_rehash(self):
return True
def __bool__(self):
return False
# An alternative approach for the behaviour of PasswordHashAttribute would be
# to use sqlalchemy.TypeDecorator. A type decorator allows custom encoding and
# decoding of values coming from the database (when query results are loaded)
# and going into the database (when statements are executed).
#
# This has one downside: After setting e.g. user.password to a string value it
# remains a string value until the change is flushed. It is not possible to
# coerce values to PasswordHash objects as soon as they are set.
#
# This is too inconsistent. Code should be able to rely on user.password to
# always behave like a PasswordHash object.
class PasswordHashAttribute:
'''Descriptor for wrapping an attribute storing a password hash string
Usage example:
>>> class User:
... # Could e.g. be an SQLAlchemy.Column or just a simple attribute
... _passord_hash = None
... password = PasswordHashAttribute('_passord_hash', SHA512PasswordHash)
...
>>> user = User()
>>> type(user.password)
<class 'uffd.password_hash.InvalidPasswordHash'>
>>>
>>> user._password_hash = '{plain}my_password'
>>> type(user.password)
<class 'uffd.password_hash.InvalidPasswordHash'>
>>> user.password.needs_rehash
True
>>>
>>> user.password = 'my_password'
>>> user._passord_hash
'{sha512}3ajDRohg3LJOIoq47kQgjUPrL1/So6U4uvvTnbT/EUyYKaZL0aRxDgwCH4pBNLai+LF+zMh//nnYRZ4t8pT7AQ=='
>>> type(user.password)
<class 'uffd.password_hash.SHA512PasswordHash'>
>>>
>>> user.password = None
>>> user._passord_hash is None
True
>>> type(user.password)
<class 'uffd.password_hash.InvalidPasswordHash'>
When set to a (plaintext) password the underlying attribute is set to a hash
value for the password. When set to None the underlying attribute is also set
to None.'''
def __init__(self, attribute_name, method_cls):
self.attribute_name = attribute_name
self.method_cls = method_cls
def __get__(self, obj, objtype=None):
if obj is None:
return self
value = getattr(obj, self.attribute_name)
try:
return registry.parse(value, target_cls=self.method_cls)
except ValueError:
return InvalidPasswordHash(value)
def __set__(self, obj, value):
if value is None:
value = InvalidPasswordHash()
elif isinstance(value, str):
value = self.method_cls.from_password(value)
setattr(obj, self.attribute_name, value.value)
# Hashing method for (potentially) low entropy secrets like user passwords. Is
# usually slow and uses salting to make dictionary attacks difficult.
LowEntropyPasswordHash = Argon2PasswordHash
# Hashing method for high entropy secrets like API keys. The secrets are
# generated instead of user-selected to ensure a high level of entropy. Is
# fast and does not need salting, since dictionary attacks are not feasable
# due to high entropy.
HighEntropyPasswordHash = SHA512PasswordHash
from flask import current_app
import itsdangerous
from uffd.utils import nopad_b32decode, nopad_b32encode, nopad_urlsafe_b64decode, nopad_urlsafe_b64encode
class Remailer:
'''The remailer feature improves user privacy by hiding real mail addresses
from services and instead providing them with autogenerated pseudonymous
remailer addresses. If a service sends a mail to a remailer address, the mail
service uses an uffd API endpoint to get the real mail address and rewrites
the remailer address with it. In case of a leak of user data from a service,
the remailer addresses are useless for third-parties.
Version 2 of the remailer address format is tolerant to case conversions at
the cost of being slightly longer.'''
@property
def configured(self):
return bool(current_app.config['REMAILER_DOMAIN'])
def get_serializer(self):
secret = current_app.config['REMAILER_SECRET_KEY'] or current_app.secret_key
return itsdangerous.URLSafeSerializer(secret, salt='remailer_address_v1')
def build_v1_address(self, service_id, user_id):
payload = self.get_serializer().dumps([service_id, user_id])
return 'v1-' + payload + '@' + current_app.config['REMAILER_DOMAIN']
def build_v2_address(self, service_id, user_id):
data, sign = self.get_serializer().dumps([service_id, user_id]).split('.', 1)
data = nopad_b32encode(nopad_urlsafe_b64decode(data)).decode().lower()
sign = nopad_b32encode(nopad_urlsafe_b64decode(sign)).decode().lower()
payload = data + '-' + sign
return 'v2-' + payload + '@' + current_app.config['REMAILER_DOMAIN']
def is_remailer_domain(self, domain):
domains = {domain.lower().strip() for domain in current_app.config['REMAILER_OLD_DOMAINS']}
if current_app.config['REMAILER_DOMAIN']:
domains.add(current_app.config['REMAILER_DOMAIN'].lower().strip())
return domain.lower().strip() in domains
def parse_v1_payload(self, payload):
try:
service_id, user_id = self.get_serializer().loads(payload)
except itsdangerous.BadData:
return None
return (service_id, user_id)
def parse_v2_payload(self, payload):
data, sign = (payload.split('-', 1) + [''])[:2]
try:
data = nopad_urlsafe_b64encode(nopad_b32decode(data.upper())).decode()
sign = nopad_urlsafe_b64encode(nopad_b32decode(sign.upper())).decode()
except ValueError:
return None
payload = data + '.' + sign
try:
service_id, user_id = self.get_serializer().loads(payload)
except itsdangerous.BadData:
return None
return (service_id, user_id)
def parse_address(self, address):
if '@' not in address:
return None
local_part, domain = address.rsplit('@', 1)
if not self.is_remailer_domain(domain):
return None
prefix, payload = (local_part.strip().split('-', 1) + [''])[:2]
if prefix == 'v1':
return self.parse_v1_payload(payload)
if prefix.lower() == 'v2':
return self.parse_v2_payload(payload)
return None
remailer = Remailer()
from .views import bp as bp_ui
from .cli import bp as bp_cli
bp = [bp_ui, bp_cli]
from .views import bp as bp_ui
bp = [bp_ui]
from .views import bp as bp_ui, send_passwordreset
bp = [bp_ui]
......@@ -24,6 +24,7 @@ def sendmail(addr, subject, template_name, **kwargs):
server = smtplib.SMTP(host=current_app.config['MAIL_SERVER'], port=current_app.config['MAIL_PORT'])
if current_app.config['MAIL_USE_STARTTLS']:
server.starttls()
if current_app.config['MAIL_USERNAME']:
server.login(current_app.config['MAIL_USERNAME'], current_app.config['MAIL_PASSWORD'])
server.send_message(msg)
server.quit()
......
from .views import bp as _bp
bp = [_bp]
from flask import Blueprint, render_template, current_app, abort, request
from flask_babel import lazy_gettext, get_locale
from uffd.navbar import register_navbar
bp = Blueprint("services", __name__, template_folder='templates', url_prefix='/services')
# pylint: disable=too-many-branches
def get_services(user=None):
if not user and not current_app.config['SERVICES_PUBLIC']:
return []
services = []
for service_data in current_app.config['SERVICES']:
service_title = get_language_specific(service_data, 'title')
if not service_title:
continue
service_description = get_language_specific(service_data, 'description')
service = {
'title': service_title,
'subtitle': service_data.get('subtitle', ''),
'description': service_description,
'url': service_data.get('url', ''),
'logo_url': service_data.get('logo_url', ''),
'has_access': True,
'permission': '',
'groups': [],
'infos': [],
'links': [],
}
if service_data.get('required_group'):
if not user or not user.has_permission(service_data['required_group']):
service['has_access'] = False
for permission_data in service_data.get('permission_levels', []):
if permission_data.get('required_group'):
if not user or not user.has_permission(permission_data['required_group']):
continue
if not permission_data.get('name'):
continue
service['has_access'] = True
service['permission'] = permission_data['name']
if service_data.get('confidential', False) and not service['has_access']:
continue
for group_data in service_data.get('groups', []):
if group_data.get('required_group'):
if not user or not user.has_permission(group_data['required_group']):
continue
if not group_data.get('name'):
continue
service['groups'].append(group_data)
for info_data in service_data.get('infos', []):
if info_data.get('required_group'):
if not user or not user.has_permission(info_data['required_group']):
continue
info_title = get_language_specific(info_data, 'title')
info_html = get_language_specific(info_data, 'html')
if not info_title or not info_html:
continue
info_button_text = get_language_specific(info_data, 'button_text', info_title)
info = {
'title': info_title,
'button_text': info_button_text,
'html': info_html,
'id': '%d-%d'%(len(services), len(service['infos'])),
}
service['infos'].append(info)
for link_data in service_data.get('links', []):
if link_data.get('required_group'):
if not user or not user.has_permission(link_data['required_group']):
continue
if not link_data.get('url') or not link_data.get('title'):
continue
service['links'].append(link_data)
services.append(service)
return services
def get_language_specific(data, field_name, default =''):
return data.get(field_name + '_' + get_locale().language, data.get(field_name, default))
def services_visible():
return len(get_services(request.user)) > 0
@bp.route("/")
@register_navbar(9, lazy_gettext('Services'), icon='sitemap', blueprint=bp, visible=services_visible)
def index():
services = get_services(request.user)
if not current_app.config['SERVICES']:
abort(404)
banner = current_app.config.get('SERVICES_BANNER')
# Set the banner to None if it is not public and no user is logged in
if not (current_app.config["SERVICES_BANNER_PUBLIC"] or request.user):
banner = None
return render_template('services/overview.html', user=request.user, services=services, banner=banner)
from .views import bp as bp_ui, login_required, set_session
bp = [bp_ui]
from .views import bp as _bp
bp = [_bp]
/*!
* Bootstrap-Dark v4.0.0 (https://github.com/ForEvolve/bootstrap-dark)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@media (prefers-color-scheme:dark) {
body {
color: #d3d3d3;
background-color: #191d21
}
abbr[data-original-title],
abbr[title] {
border-bottom: 0
}
a {
color: #adadad;
background-color: transparent
}
a:hover {
color: #878787
}
a:not([href]):not([class]) {
color: inherit
}
a:not([href]):not([class]):hover {
color: inherit
}
caption {
color: #6c757d
}
fieldset {
border: 0
}
legend {
color: inherit
}
hr {
border: 0;
border-top: 1px solid rgba(255,255,255,.1)
}
.mark,
mark {
background-color: #fcf8e3
}
.blockquote-footer {
color: #6c757d
}
.img-thumbnail {
background-color: #fff;
border: 1px solid #dee2e6
}
.figure-caption {
color: #6c757d
}
code {
color: #e83e8c
}
a > code {
color: inherit
}
kbd {
color: #fff;
background-color: #212529
}
pre {
color: #f8f9fa
}
pre code {
color: inherit
}
}
@media (prefers-color-scheme:dark) {
.table {
color: #d3d3d3
}
.table td,
.table th {
border-top: 1px solid #343a40
}
.table thead th {
border-bottom: 2px solid #343a40
}
.table tbody + tbody {
border-top: 2px solid #343a40
}
.table-bordered {
border: 1px solid #343a40
}
.table-bordered td,
.table-bordered th {
border: 1px solid #343a40
}
.table-borderless tbody + tbody,
.table-borderless td,
.table-borderless th,
.table-borderless thead th {
border: 0
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0,0,0,.05)
}
.table-hover tbody tr:hover {
color: #d3d3d3;
background-color: rgba(0,0,0,.075)
}
.table-primary,
.table-primary > td,
.table-primary > th {
background-color: #c6e1ff
}
.table-primary tbody + tbody,
.table-primary td,
.table-primary th,
.table-primary thead th {
border-color: #95c8ff
}
.table-hover .table-primary:hover {
background-color: #add4ff
}
.table-hover .table-primary:hover > td,
.table-hover .table-primary:hover > th {
background-color: #add4ff
}
.table-secondary,
.table-secondary > td,
.table-secondary > th {
background-color: #d6d8db
}
.table-secondary tbody + tbody,
.table-secondary td,
.table-secondary th,
.table-secondary thead th {
border-color: #b3b7bb
}
.table-hover .table-secondary:hover {
background-color: #c8cbcf
}
.table-hover .table-secondary:hover > td,
.table-hover .table-secondary:hover > th {
background-color: #c8cbcf
}
.table-success,
.table-success > td,
.table-success > th {
background-color: #c3e6cb
}
.table-success tbody + tbody,
.table-success td,
.table-success th,
.table-success thead th {
border-color: #8fd19e
}
.table-hover .table-success:hover {
background-color: #b1dfbb
}
.table-hover .table-success:hover > td,
.table-hover .table-success:hover > th {
background-color: #b1dfbb
}
.table-info,
.table-info > td,
.table-info > th {
background-color: #bee5eb
}
.table-info tbody + tbody,
.table-info td,
.table-info th,
.table-info thead th {
border-color: #86cfda
}
.table-hover .table-info:hover {
background-color: #abdde5
}
.table-hover .table-info:hover > td,
.table-hover .table-info:hover > th {
background-color: #abdde5
}
.table-warning,
.table-warning > td,
.table-warning > th {
background-color: #fedbbd
}
.table-warning tbody + tbody,
.table-warning td,
.table-warning th,
.table-warning thead th {
border-color: #febc85
}
.table-hover .table-warning:hover {
background-color: #fecda4
}
.table-hover .table-warning:hover > td,
.table-hover .table-warning:hover > th {
background-color: #fecda4
}
.table-danger,
.table-danger > td,
.table-danger > th {
background-color: #f5c6cb
}
.table-danger tbody + tbody,
.table-danger td,
.table-danger th,
.table-danger thead th {
border-color: #ed969e
}
.table-hover .table-danger:hover {
background-color: #f1b0b7
}
.table-hover .table-danger:hover > td,
.table-hover .table-danger:hover > th {
background-color: #f1b0b7
}
.table-light,
.table-light > td,
.table-light > th {
background-color: #f6f7f8
}
.table-light tbody + tbody,
.table-light td,
.table-light th,
.table-light thead th {
border-color: #eef0f2
}
.table-hover .table-light:hover {
background-color: #e8eaed
}
.table-hover .table-light:hover > td,
.table-hover .table-light:hover > th {
background-color: #e8eaed
}
.table-dark,
.table-dark > td,
.table-dark > th {
background-color: #c6c8ca
}
.table-dark tbody + tbody,
.table-dark td,
.table-dark th,
.table-dark thead th {
border-color: #95999c
}
.table-hover .table-dark:hover {
background-color: #b9bbbe
}
.table-hover .table-dark:hover > td,
.table-hover .table-dark:hover > th {
background-color: #b9bbbe
}
.table-active,
.table-active > td,
.table-active > th {
background-color: rgba(0,0,0,.075)
}
.table-hover .table-active:hover {
background-color: rgba(0,0,0,.075)
}
.table-hover .table-active:hover > td,
.table-hover .table-active:hover > th {
background-color: rgba(0,0,0,.075)
}
.table .thead-dark th {
color: #dee2e6;
background-color: #343a40;
border-color: #454d55
}
.table .thead-light th {
color: #495057;
background-color: #e9ecef;
border-color: #343a40
}
.table-dark {
color: #dee2e6;
background-color: #343a40
}
.table-dark td,
.table-dark th,
.table-dark thead th {
border-color: #454d55
}
.table-dark.table-bordered {
border: 0
}
.table-dark.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(255,255,255,.05)
}
.table-dark.table-hover tbody tr:hover {
color: #fff;
background-color: rgba(255,255,255,.075)
}
}
@media (prefers-color-scheme:dark) and (max-width:575.98px) {
.table-responsive-sm > .table-bordered {
border: 0
}
}
@media (prefers-color-scheme:dark) and (max-width:767.98px) {
.table-responsive-md > .table-bordered {
border: 0
}
}
@media (prefers-color-scheme:dark) and (max-width:991.98px) {
.table-responsive-lg > .table-bordered {
border: 0
}
}
@media (prefers-color-scheme:dark) and (max-width:1199.98px) {
.table-responsive-xl > .table-bordered {
border: 0
}
}
@media (prefers-color-scheme:dark) {
.table-responsive > .table-bordered {
border: 0
}
.table-primary,
.table-primary > td,
.table-primary > th {
color: #343a40
}
.table-hover .table-primary:hover {
color: #343a40
}
.table-hover .table-primary:hover > td,
.table-hover .table-primary:hover > th {
color: #343a40
}
.table-secondary,
.table-secondary > td,
.table-secondary > th {
color: #343a40
}
.table-hover .table-secondary:hover {
color: #343a40
}
.table-hover .table-secondary:hover > td,
.table-hover .table-secondary:hover > th {
color: #343a40
}
.table-success,
.table-success > td,
.table-success > th {
color: #343a40
}
.table-hover .table-success:hover {
color: #343a40
}
.table-hover .table-success:hover > td,
.table-hover .table-success:hover > th {
color: #343a40
}
.table-info,
.table-info > td,
.table-info > th {
color: #343a40
}
.table-hover .table-info:hover {
color: #343a40
}
.table-hover .table-info:hover > td,
.table-hover .table-info:hover > th {
color: #343a40
}
.table-warning,
.table-warning > td,
.table-warning > th {
color: #343a40
}
.table-hover .table-warning:hover {
color: #343a40
}
.table-hover .table-warning:hover > td,
.table-hover .table-warning:hover > th {
color: #343a40
}
.table-danger,
.table-danger > td,
.table-danger > th {
color: #343a40
}
.table-hover .table-danger:hover {
color: #343a40
}
.table-hover .table-danger:hover > td,
.table-hover .table-danger:hover > th {
color: #343a40
}
.table-light,
.table-light > td,
.table-light > th {
color: #343a40
}
.table-hover .table-light:hover {
color: #343a40
}
.table-hover .table-light:hover > td,
.table-hover .table-light:hover > th {
color: #343a40
}
.table-dark,
.table-dark > td,
.table-dark > th {
color: #343a40
}
.table-hover .table-dark:hover {
color: #343a40
}
.table-hover .table-dark:hover > td,
.table-hover .table-dark:hover > th {
color: #343a40
}
.table-active,
.table-active > td,
.table-active > th {
color: #e9ecef
}
.table-hover .table-active:hover {
color: #e9ecef
}
.table-hover .table-active:hover > td,
.table-hover .table-active:hover > th {
color: #e9ecef
}
.table-dark {
color: #dee2e6
}
.form-control {
color: #dee2e6;
background-color: #000;
border: 1px solid #6c757d
}
}
@media (prefers-color-scheme:dark) {
.form-control::-ms-expand {
background-color: transparent;
border: 0
}
.form-control:focus {
color: #dee2e6;
background-color: #191d21;
border-color: #b3d7ff;
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.form-control::-webkit-input-placeholder {
color: #6c757d
}
.form-control::-moz-placeholder {
color: #6c757d
}
.form-control::-ms-input-placeholder {
color: #6c757d
}
.form-control::placeholder {
color: #6c757d
}
.form-control:disabled,
.form-control[readonly] {
background-color: #343a40
}
select.form-control:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #dee2e6
}
select.form-control:focus::-ms-value {
color: #dee2e6;
background-color: #000
}
.form-control-plaintext {
color: #212529;
background-color: transparent;
border: solid transparent
}
.form-check-input:disabled ~ .form-check-label,
.form-check-input[disabled] ~ .form-check-label {
color: #6c757d
}
.valid-feedback {
color: #28a745
}
.valid-tooltip {
color: #e9ecef;
background-color: rgba(40,167,69,.9)
}
.form-control.is-valid,
.was-validated .form-control:valid {
border-color: #28a745;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e")
}
.form-control.is-valid:focus,
.was-validated .form-control:valid:focus {
border-color: #28a745;
box-shadow: 0 0 0 .2rem rgba(40,167,69,.25)
}
.custom-select.is-valid,
.was-validated .custom-select:valid {
border-color: #28a745;
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat
}
.custom-select.is-valid:focus,
.was-validated .custom-select:valid:focus {
border-color: #28a745;
box-shadow: 0 0 0 .2rem rgba(40,167,69,.25)
}
.form-check-input.is-valid ~ .form-check-label,
.was-validated .form-check-input:valid ~ .form-check-label {
color: #28a745
}
.custom-control-input.is-valid ~ .custom-control-label,
.was-validated .custom-control-input:valid ~ .custom-control-label {
color: #28a745
}
.custom-control-input.is-valid ~ .custom-control-label::before,
.was-validated .custom-control-input:valid ~ .custom-control-label::before {
border-color: #28a745
}
.custom-control-input.is-valid:checked ~ .custom-control-label::before,
.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before {
border-color: #34ce57;
background-color: #34ce57
}
.custom-control-input.is-valid:focus ~ .custom-control-label::before,
.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before {
box-shadow: 0 0 0 .2rem rgba(40,167,69,.25)
}
.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before,
.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before {
border-color: #28a745
}
.custom-file-input.is-valid ~ .custom-file-label,
.was-validated .custom-file-input:valid ~ .custom-file-label {
border-color: #28a745
}
.custom-file-input.is-valid:focus ~ .custom-file-label,
.was-validated .custom-file-input:valid:focus ~ .custom-file-label {
border-color: #28a745;
box-shadow: 0 0 0 .2rem rgba(40,167,69,.25)
}
.invalid-feedback {
color: #dc3545
}
.invalid-tooltip {
color: #e9ecef;
background-color: rgba(220,53,69,.9)
}
.form-control.is-invalid,
.was-validated .form-control:invalid {
border-color: #dc3545;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e")
}
.form-control.is-invalid:focus,
.was-validated .form-control:invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 .2rem rgba(220,53,69,.25)
}
.custom-select.is-invalid,
.was-validated .custom-select:invalid {
border-color: #dc3545;
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat
}
.custom-select.is-invalid:focus,
.was-validated .custom-select:invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 .2rem rgba(220,53,69,.25)
}
.form-check-input.is-invalid ~ .form-check-label,
.was-validated .form-check-input:invalid ~ .form-check-label {
color: #dc3545
}
.custom-control-input.is-invalid ~ .custom-control-label,
.was-validated .custom-control-input:invalid ~ .custom-control-label {
color: #dc3545
}
.custom-control-input.is-invalid ~ .custom-control-label::before,
.was-validated .custom-control-input:invalid ~ .custom-control-label::before {
border-color: #dc3545
}
.custom-control-input.is-invalid:checked ~ .custom-control-label::before,
.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before {
border-color: #e4606d;
background-color: #e4606d
}
.custom-control-input.is-invalid:focus ~ .custom-control-label::before,
.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before {
box-shadow: 0 0 0 .2rem rgba(220,53,69,.25)
}
.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before,
.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before {
border-color: #dc3545
}
.custom-file-input.is-invalid ~ .custom-file-label,
.was-validated .custom-file-input:invalid ~ .custom-file-label {
border-color: #dc3545
}
.custom-file-input.is-invalid:focus ~ .custom-file-label,
.was-validated .custom-file-input:invalid:focus ~ .custom-file-label {
border-color: #dc3545;
box-shadow: 0 0 0 .2rem rgba(220,53,69,.25)
}
}
@media (prefers-color-scheme:dark) {
.btn {
color: #d3d3d3;
background-color: transparent;
border: 1px solid transparent
}
}
@media (prefers-color-scheme:dark) {
.btn:hover {
color: #d3d3d3
}
.btn.focus,
.btn:focus {
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.btn-primary {
color: #e9ecef;
background-color: #3395ff;
border-color: #3395ff
}
.btn-primary:hover {
color: #e9ecef;
background-color: #0d82ff;
border-color: #007bff
}
.btn-primary.focus,
.btn-primary:focus {
color: #e9ecef;
background-color: #0d82ff;
border-color: #007bff;
box-shadow: 0 0 0 .2rem rgba(78,162,253,.5)
}
.btn-primary.disabled,
.btn-primary:disabled {
color: #e9ecef;
background-color: #3395ff;
border-color: #3395ff
}
.btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active,
.show > .btn-primary.dropdown-toggle {
color: #e9ecef;
background-color: #007bff;
border-color: #0075f2
}
.btn-primary:not(:disabled):not(.disabled).active:focus,
.btn-primary:not(:disabled):not(.disabled):active:focus,
.show > .btn-primary.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(78,162,253,.5)
}
.btn-secondary {
color: #e9ecef;
background-color: #6c757d;
border-color: #6c757d
}
.btn-secondary:hover {
color: #e9ecef;
background-color: #5a6268;
border-color: #545b62
}
.btn-secondary.focus,
.btn-secondary:focus {
color: #e9ecef;
background-color: #5a6268;
border-color: #545b62;
box-shadow: 0 0 0 .2rem rgba(127,135,142,.5)
}
.btn-secondary.disabled,
.btn-secondary:disabled {
color: #e9ecef;
background-color: #6c757d;
border-color: #6c757d
}
.btn-secondary:not(:disabled):not(.disabled).active,
.btn-secondary:not(:disabled):not(.disabled):active,
.show > .btn-secondary.dropdown-toggle {
color: #e9ecef;
background-color: #545b62;
border-color: #4e555b
}
.btn-secondary:not(:disabled):not(.disabled).active:focus,
.btn-secondary:not(:disabled):not(.disabled):active:focus,
.show > .btn-secondary.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(127,135,142,.5)
}
.btn-success {
color: #e9ecef;
background-color: #28a745;
border-color: #28a745
}
.btn-success:hover {
color: #e9ecef;
background-color: #218838;
border-color: #1e7e34
}
.btn-success.focus,
.btn-success:focus {
color: #e9ecef;
background-color: #218838;
border-color: #1e7e34;
box-shadow: 0 0 0 .2rem rgba(69,177,95,.5)
}
.btn-success.disabled,
.btn-success:disabled {
color: #e9ecef;
background-color: #28a745;
border-color: #28a745
}
.btn-success:not(:disabled):not(.disabled).active,
.btn-success:not(:disabled):not(.disabled):active,
.show > .btn-success.dropdown-toggle {
color: #e9ecef;
background-color: #1e7e34;
border-color: #1c7430
}
.btn-success:not(:disabled):not(.disabled).active:focus,
.btn-success:not(:disabled):not(.disabled):active:focus,
.show > .btn-success.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(69,177,95,.5)
}
.btn-info {
color: #e9ecef;
background-color: #17a2b8;
border-color: #17a2b8
}
.btn-info:hover {
color: #e9ecef;
background-color: #138496;
border-color: #117a8b
}
.btn-info.focus,
.btn-info:focus {
color: #e9ecef;
background-color: #138496;
border-color: #117a8b;
box-shadow: 0 0 0 .2rem rgba(55,173,192,.5)
}
.btn-info.disabled,
.btn-info:disabled {
color: #e9ecef;
background-color: #17a2b8;
border-color: #17a2b8
}
.btn-info:not(:disabled):not(.disabled).active,
.btn-info:not(:disabled):not(.disabled):active,
.show > .btn-info.dropdown-toggle {
color: #e9ecef;
background-color: #117a8b;
border-color: #10707f
}
.btn-info:not(:disabled):not(.disabled).active:focus,
.btn-info:not(:disabled):not(.disabled):active:focus,
.show > .btn-info.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(55,173,192,.5)
}
.btn-warning {
color: #343a40;
background-color: #fd7e14;
border-color: #fd7e14
}
.btn-warning:hover {
color: #e9ecef;
background-color: #e96b02;
border-color: #dc6502
}
.btn-warning.focus,
.btn-warning:focus {
color: #e9ecef;
background-color: #e96b02;
border-color: #dc6502;
box-shadow: 0 0 0 .2rem rgba(223,116,27,.5)
}
.btn-warning.disabled,
.btn-warning:disabled {
color: #343a40;
background-color: #fd7e14;
border-color: #fd7e14
}
.btn-warning:not(:disabled):not(.disabled).active,
.btn-warning:not(:disabled):not(.disabled):active,
.show > .btn-warning.dropdown-toggle {
color: #e9ecef;
background-color: #dc6502;
border-color: #cf5f02
}
.btn-warning:not(:disabled):not(.disabled).active:focus,
.btn-warning:not(:disabled):not(.disabled):active:focus,
.show > .btn-warning.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(223,116,27,.5)
}
.btn-danger {
color: #e9ecef;
background-color: #dc3545;
border-color: #dc3545
}
.btn-danger:hover {
color: #e9ecef;
background-color: #c82333;
border-color: #bd2130
}
.btn-danger.focus,
.btn-danger:focus {
color: #e9ecef;
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 0 0 .2rem rgba(222,80,95,.5)
}
.btn-danger.disabled,
.btn-danger:disabled {
color: #e9ecef;
background-color: #dc3545;
border-color: #dc3545
}
.btn-danger:not(:disabled):not(.disabled).active,
.btn-danger:not(:disabled):not(.disabled):active,
.show > .btn-danger.dropdown-toggle {
color: #e9ecef;
background-color: #bd2130;
border-color: #b21f2d
}
.btn-danger:not(:disabled):not(.disabled).active:focus,
.btn-danger:not(:disabled):not(.disabled):active:focus,
.show > .btn-danger.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(222,80,95,.5)
}
.btn-light {
color: #343a40;
background-color: #dee2e6;
border-color: #dee2e6
}
.btn-light:hover {
color: #343a40;
background-color: #c8cfd6;
border-color: #c1c9d0
}
.btn-light.focus,
.btn-light:focus {
color: #343a40;
background-color: #c8cfd6;
border-color: #c1c9d0;
box-shadow: 0 0 0 .2rem rgba(197,201,205,.5)
}
.btn-light.disabled,
.btn-light:disabled {
color: #343a40;
background-color: #dee2e6;
border-color: #dee2e6
}
.btn-light:not(:disabled):not(.disabled).active,
.btn-light:not(:disabled):not(.disabled):active,
.show > .btn-light.dropdown-toggle {
color: #343a40;
background-color: #c1c9d0;
border-color: #bac2cb
}
.btn-light:not(:disabled):not(.disabled).active:focus,
.btn-light:not(:disabled):not(.disabled):active:focus,
.show > .btn-light.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(197,201,205,.5)
}
.btn-dark {
color: #e9ecef;
background-color: #343a40;
border-color: #343a40
}
.btn-dark:hover {
color: #e9ecef;
background-color: #23272b;
border-color: #1d2124
}
.btn-dark.focus,
.btn-dark:focus {
color: #e9ecef;
background-color: #23272b;
border-color: #1d2124;
box-shadow: 0 0 0 .2rem rgba(79,85,90,.5)
}
.btn-dark.disabled,
.btn-dark:disabled {
color: #e9ecef;
background-color: #343a40;
border-color: #343a40
}
.btn-dark:not(:disabled):not(.disabled).active,
.btn-dark:not(:disabled):not(.disabled):active,
.show > .btn-dark.dropdown-toggle {
color: #e9ecef;
background-color: #1d2124;
border-color: #171a1d
}
.btn-dark:not(:disabled):not(.disabled).active:focus,
.btn-dark:not(:disabled):not(.disabled):active:focus,
.show > .btn-dark.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(79,85,90,.5)
}
.btn-outline-primary {
color: #3395ff;
border-color: #3395ff
}
.btn-outline-primary:hover {
color: #e9ecef;
background-color: #3395ff;
border-color: #3395ff
}
.btn-outline-primary.focus,
.btn-outline-primary:focus {
box-shadow: 0 0 0 .2rem rgba(51,149,255,.5)
}
.btn-outline-primary.disabled,
.btn-outline-primary:disabled {
color: #3395ff;
background-color: transparent
}
.btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active,
.show > .btn-outline-primary.dropdown-toggle {
color: #e9ecef;
background-color: #3395ff;
border-color: #3395ff
}
.btn-outline-primary:not(:disabled):not(.disabled).active:focus,
.btn-outline-primary:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-primary.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(51,149,255,.5)
}
.btn-outline-secondary {
color: #6c757d;
border-color: #6c757d
}
.btn-outline-secondary:hover {
color: #e9ecef;
background-color: #6c757d;
border-color: #6c757d
}
.btn-outline-secondary.focus,
.btn-outline-secondary:focus {
box-shadow: 0 0 0 .2rem rgba(108,117,125,.5)
}
.btn-outline-secondary.disabled,
.btn-outline-secondary:disabled {
color: #6c757d;
background-color: transparent
}
.btn-outline-secondary:not(:disabled):not(.disabled).active,
.btn-outline-secondary:not(:disabled):not(.disabled):active,
.show > .btn-outline-secondary.dropdown-toggle {
color: #e9ecef;
background-color: #6c757d;
border-color: #6c757d
}
.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-secondary.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(108,117,125,.5)
}
.btn-outline-success {
color: #28a745;
border-color: #28a745
}
.btn-outline-success:hover {
color: #e9ecef;
background-color: #28a745;
border-color: #28a745
}
.btn-outline-success.focus,
.btn-outline-success:focus {
box-shadow: 0 0 0 .2rem rgba(40,167,69,.5)
}
.btn-outline-success.disabled,
.btn-outline-success:disabled {
color: #28a745;
background-color: transparent
}
.btn-outline-success:not(:disabled):not(.disabled).active,
.btn-outline-success:not(:disabled):not(.disabled):active,
.show > .btn-outline-success.dropdown-toggle {
color: #e9ecef;
background-color: #28a745;
border-color: #28a745
}
.btn-outline-success:not(:disabled):not(.disabled).active:focus,
.btn-outline-success:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-success.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(40,167,69,.5)
}
.btn-outline-info {
color: #17a2b8;
border-color: #17a2b8
}
.btn-outline-info:hover {
color: #e9ecef;
background-color: #17a2b8;
border-color: #17a2b8
}
.btn-outline-info.focus,
.btn-outline-info:focus {
box-shadow: 0 0 0 .2rem rgba(23,162,184,.5)
}
.btn-outline-info.disabled,
.btn-outline-info:disabled {
color: #17a2b8;
background-color: transparent
}
.btn-outline-info:not(:disabled):not(.disabled).active,
.btn-outline-info:not(:disabled):not(.disabled):active,
.show > .btn-outline-info.dropdown-toggle {
color: #e9ecef;
background-color: #17a2b8;
border-color: #17a2b8
}
.btn-outline-info:not(:disabled):not(.disabled).active:focus,
.btn-outline-info:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-info.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(23,162,184,.5)
}
.btn-outline-warning {
color: #fd7e14;
border-color: #fd7e14
}
.btn-outline-warning:hover {
color: #343a40;
background-color: #fd7e14;
border-color: #fd7e14
}
.btn-outline-warning.focus,
.btn-outline-warning:focus {
box-shadow: 0 0 0 .2rem rgba(253,126,20,.5)
}
.btn-outline-warning.disabled,
.btn-outline-warning:disabled {
color: #fd7e14;
background-color: transparent
}
.btn-outline-warning:not(:disabled):not(.disabled).active,
.btn-outline-warning:not(:disabled):not(.disabled):active,
.show > .btn-outline-warning.dropdown-toggle {
color: #343a40;
background-color: #fd7e14;
border-color: #fd7e14
}
.btn-outline-warning:not(:disabled):not(.disabled).active:focus,
.btn-outline-warning:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-warning.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(253,126,20,.5)
}
.btn-outline-danger {
color: #dc3545;
border-color: #dc3545
}
.btn-outline-danger:hover {
color: #e9ecef;
background-color: #dc3545;
border-color: #dc3545
}
.btn-outline-danger.focus,
.btn-outline-danger:focus {
box-shadow: 0 0 0 .2rem rgba(220,53,69,.5)
}
.btn-outline-danger.disabled,
.btn-outline-danger:disabled {
color: #dc3545;
background-color: transparent
}
.btn-outline-danger:not(:disabled):not(.disabled).active,
.btn-outline-danger:not(:disabled):not(.disabled):active,
.show > .btn-outline-danger.dropdown-toggle {
color: #e9ecef;
background-color: #dc3545;
border-color: #dc3545
}
.btn-outline-danger:not(:disabled):not(.disabled).active:focus,
.btn-outline-danger:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-danger.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(220,53,69,.5)
}
.btn-outline-light {
color: #dee2e6;
border-color: #dee2e6
}
.btn-outline-light:hover {
color: #343a40;
background-color: #dee2e6;
border-color: #dee2e6
}
.btn-outline-light.focus,
.btn-outline-light:focus {
box-shadow: 0 0 0 .2rem rgba(222,226,230,.5)
}
.btn-outline-light.disabled,
.btn-outline-light:disabled {
color: #dee2e6;
background-color: transparent
}
.btn-outline-light:not(:disabled):not(.disabled).active,
.btn-outline-light:not(:disabled):not(.disabled):active,
.show > .btn-outline-light.dropdown-toggle {
color: #343a40;
background-color: #dee2e6;
border-color: #dee2e6
}
.btn-outline-light:not(:disabled):not(.disabled).active:focus,
.btn-outline-light:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-light.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(222,226,230,.5)
}
.btn-outline-dark {
color: #343a40;
border-color: #343a40
}
.btn-outline-dark:hover {
color: #e9ecef;
background-color: #343a40;
border-color: #343a40
}
.btn-outline-dark.focus,
.btn-outline-dark:focus {
box-shadow: 0 0 0 .2rem rgba(52,58,64,.5)
}
.btn-outline-dark.disabled,
.btn-outline-dark:disabled {
color: #343a40;
background-color: transparent
}
.btn-outline-dark:not(:disabled):not(.disabled).active,
.btn-outline-dark:not(:disabled):not(.disabled):active,
.show > .btn-outline-dark.dropdown-toggle {
color: #e9ecef;
background-color: #343a40;
border-color: #343a40
}
.btn-outline-dark:not(:disabled):not(.disabled).active:focus,
.btn-outline-dark:not(:disabled):not(.disabled):active:focus,
.show > .btn-outline-dark.dropdown-toggle:focus {
box-shadow: 0 0 0 .2rem rgba(52,58,64,.5)
}
.btn-link {
color: #adadad
}
.btn-link:hover {
color: #878787
}
.btn-link.disabled,
.btn-link:disabled {
color: #6c757d
}
}
@media (prefers-color-scheme:dark) {
.dropdown-toggle::after {
border-top: .3em solid;
border-right: .3em solid transparent;
border-bottom: 0;
border-left: .3em solid transparent
}
.dropdown-menu {
color: #d3d3d3;
background-color: #000;
border: 1px solid rgba(255,255,255,.15)
}
}
@media (prefers-color-scheme:dark) {
.dropup .dropdown-toggle::after {
border-top: 0;
border-right: .3em solid transparent;
border-bottom: .3em solid;
border-left: .3em solid transparent
}
.dropright .dropdown-toggle::after {
border-top: .3em solid transparent;
border-right: 0;
border-bottom: .3em solid transparent;
border-left: .3em solid
}
.dropleft .dropdown-toggle::before {
border-top: .3em solid transparent;
border-right: .3em solid;
border-bottom: .3em solid transparent
}
.dropdown-divider {
border-top: 1px solid #343a40
}
.dropdown-item {
color: #f8f9fa;
background-color: transparent;
border: 0
}
.dropdown-item:focus,
.dropdown-item:hover {
color: #fff;
background-color: #212529
}
.dropdown-item.active,
.dropdown-item:active {
color: #000;
background-color: #3395ff
}
.dropdown-item.disabled,
.dropdown-item:disabled {
color: #ced4da;
background-color: transparent
}
.dropdown-header {
color: #ced4da
}
.dropdown-item-text {
color: #f8f9fa
}
.input-group-text {
color: #dee2e6;
background-color: #343a40;
border: 1px solid #6c757d
}
.custom-control-input:checked ~ .custom-control-label::before {
color: #fff;
border-color: #007bff;
background-color: #007bff
}
.custom-control-input:focus ~ .custom-control-label::before {
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {
border-color: #80bdff
}
.custom-control-input:not(:disabled):active ~ .custom-control-label::before {
color: #fff;
background-color: #b3d7ff;
border-color: #b3d7ff
}
.custom-control-input:disabled ~ .custom-control-label,
.custom-control-input[disabled] ~ .custom-control-label {
color: #6c757d
}
.custom-control-input:disabled ~ .custom-control-label::before,
.custom-control-input[disabled] ~ .custom-control-label::before {
background-color: #e9ecef
}
.custom-control-label::before {
background-color: #fff;
border: 1px solid #adb5bd
}
.custom-control-label::after {
background: 50%/50% 50% no-repeat
}
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")
}
.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {
border-color: #007bff;
background-color: #007bff
}
.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")
}
.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {
background-color: rgba(0,123,255,.5)
}
.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {
background-color: rgba(0,123,255,.5)
}
.custom-radio .custom-control-input:checked ~ .custom-control-label::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")
}
.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {
background-color: rgba(0,123,255,.5)
}
.custom-switch .custom-control-label::after {
background-color: #adb5bd
}
}
@media (prefers-color-scheme:dark) {
.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
background-color: #fff
}
.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {
background-color: rgba(0,123,255,.5)
}
.custom-select {
color: #dee2e6;
background: #000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;
border: 1px solid #6c757d
}
.custom-select:focus {
border-color: #80bdff;
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-select:focus::-ms-value {
color: #dee2e6;
background-color: #000
}
.custom-select[multiple],
.custom-select[size]:not([size="1"]) {
background-image: none
}
.custom-select:disabled {
color: #ced4da;
background-color: #343a40
}
.custom-select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #dee2e6
}
.custom-file-input:focus ~ .custom-file-label {
border-color: #80bdff;
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-file-input:disabled ~ .custom-file-label,
.custom-file-input[disabled] ~ .custom-file-label {
background-color: #e9ecef
}
.custom-file-label {
color: #495057;
background-color: #fff;
border: 1px solid #ced4da
}
.custom-file-label::after {
color: #495057;
background-color: #e9ecef;
border-left: inherit
}
.custom-range {
background-color: transparent
}
.custom-range:focus::-webkit-slider-thumb {
box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-range:focus::-moz-range-thumb {
box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-range:focus::-ms-thumb {
box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)
}
.custom-range::-moz-focus-outer {
border: 0
}
.custom-range::-webkit-slider-thumb {
background-color: #007bff;
border: 0
}
}
@media (prefers-color-scheme:dark) {
.custom-range::-webkit-slider-thumb:active {
background-color: #b3d7ff
}
.custom-range::-webkit-slider-runnable-track {
color: transparent;
background-color: #dee2e6;
border-color: transparent
}
.custom-range::-moz-range-thumb {
background-color: #007bff;
border: 0
}
}
@media (prefers-color-scheme:dark) {
.custom-range::-moz-range-thumb:active {
background-color: #b3d7ff
}
.custom-range::-moz-range-track {
color: transparent;
background-color: #dee2e6;
border-color: transparent
}
.custom-range::-ms-thumb {
background-color: #007bff;
border: 0
}
}
@media (prefers-color-scheme:dark) {
.custom-range::-ms-thumb:active {
background-color: #b3d7ff
}
.custom-range::-ms-track {
color: transparent;
background-color: transparent;
border-color: transparent
}
.custom-range::-ms-fill-lower {
background-color: #dee2e6
}
.custom-range::-ms-fill-upper {
background-color: #dee2e6
}
.custom-range:disabled::-webkit-slider-thumb {
background-color: #adb5bd
}
.custom-range:disabled::-moz-range-thumb {
background-color: #adb5bd
}
.custom-range:disabled::-ms-thumb {
background-color: #adb5bd
}
}
@media (prefers-color-scheme:dark) {
.nav-link.disabled {
color: #6c757d
}
.nav-tabs {
border-bottom: 1px solid rgba(255,255,255,.125)
}
.nav-tabs .nav-link {
background-color: transparent;
border: 1px solid transparent
}
.nav-tabs .nav-link:focus,
.nav-tabs .nav-link:hover {
border-color: #495057 #495057 rgba(255,255,255,.125)
}
.nav-tabs .nav-link.disabled {
color: #6c757d;
background-color: transparent;
border-color: transparent
}
.nav-tabs .nav-item.show .nav-link,
.nav-tabs .nav-link.active {
color: #f8f9fa;
background-color: #191d21;
border-color: #495057 #495057 #191d21
}
.nav-pills .nav-link {
background: 0 0;
border: 0
}
.nav-pills .nav-link.active,
.nav-pills .show > .nav-link {
color: #fff;
background-color: #007bff
}
.navbar-toggler {
background-color: transparent;
border: 1px solid transparent
}
.navbar-toggler-icon {
background: 50%/100% 100% no-repeat
}
}
@media (prefers-color-scheme:dark) {
.navbar-light .navbar-brand {
color: rgba(0,0,0,.9)
}
.navbar-light .navbar-brand:focus,
.navbar-light .navbar-brand:hover {
color: rgba(0,0,0,.9)
}
.navbar-light .navbar-nav .nav-link {
color: rgba(0,0,0,.5)
}
.navbar-light .navbar-nav .nav-link:focus,
.navbar-light .navbar-nav .nav-link:hover {
color: rgba(0,0,0,.7)
}
.navbar-light .navbar-nav .nav-link.disabled {
color: rgba(0,0,0,.3)
}
.navbar-light .navbar-nav .active > .nav-link,
.navbar-light .navbar-nav .nav-link.active,
.navbar-light .navbar-nav .nav-link.show,
.navbar-light .navbar-nav .show > .nav-link {
color: rgba(0,0,0,.9)
}
.navbar-light .navbar-toggler {
color: rgba(0,0,0,.5);
border-color: rgba(0,0,0,.1)
}
.navbar-light .navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")
}
.navbar-light .navbar-text {
color: rgba(0,0,0,.5)
}
.navbar-light .navbar-text a {
color: rgba(0,0,0,.9)
}
.navbar-light .navbar-text a:focus,
.navbar-light .navbar-text a:hover {
color: rgba(0,0,0,.9)
}
.navbar-dark .navbar-brand,
.navbar-themed .navbar-brand {
color: #fff
}
.navbar-dark .navbar-brand:focus,
.navbar-dark .navbar-brand:hover,
.navbar-themed .navbar-brand:focus,
.navbar-themed .navbar-brand:hover {
color: #fff
}
.navbar-dark .navbar-nav .nav-link,
.navbar-themed .navbar-nav .nav-link {
color: rgba(255,255,255,.5)
}
.navbar-dark .navbar-nav .nav-link:focus,
.navbar-dark .navbar-nav .nav-link:hover,
.navbar-themed .navbar-nav .nav-link:focus,
.navbar-themed .navbar-nav .nav-link:hover {
color: rgba(255,255,255,.75)
}
.navbar-dark .navbar-nav .nav-link.disabled,
.navbar-themed .navbar-nav .nav-link.disabled {
color: rgba(255,255,255,.25)
}
.navbar-dark .navbar-nav .active > .nav-link,
.navbar-dark .navbar-nav .nav-link.active,
.navbar-dark .navbar-nav .nav-link.show,
.navbar-dark .navbar-nav .show > .nav-link,
.navbar-themed .navbar-nav .active > .nav-link,
.navbar-themed .navbar-nav .nav-link.active,
.navbar-themed .navbar-nav .nav-link.show,
.navbar-themed .navbar-nav .show > .nav-link {
color: #fff
}
.navbar-dark .navbar-toggler,
.navbar-themed .navbar-toggler {
color: rgba(255,255,255,.5);
border-color: rgba(255,255,255,.1)
}
.navbar-dark .navbar-toggler-icon,
.navbar-themed .navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")
}
.navbar-dark .navbar-text,
.navbar-themed .navbar-text {
color: rgba(255,255,255,.5)
}
.navbar-dark .navbar-text a,
.navbar-themed .navbar-text a {
color: #fff
}
.navbar-dark .navbar-text a:focus,
.navbar-dark .navbar-text a:hover,
.navbar-themed .navbar-text a:focus,
.navbar-themed .navbar-text a:hover {
color: #fff
}
.card {
background-color: #212529;
border: 1px solid rgba(255,255,255,.125)
}
.card > .list-group {
border-top: inherit;
border-bottom: inherit
}
.card > .card-header + .list-group,
.card > .list-group + .card-footer {
border-top: 0
}
.card-header {
background-color: rgba(255,255,255,.03);
border-bottom: 1px solid rgba(255,255,255,.125)
}
.card-footer {
background-color: rgba(255,255,255,.03);
border-top: 1px solid rgba(255,255,255,.125)
}
.card-header-tabs {
border-bottom: 0
}
}
@media (prefers-color-scheme:dark) and (min-width:576px) {
.card-group > .card + .card {
border-left: 0
}
}
@media (prefers-color-scheme:dark) {
.accordion > .card:not(:last-of-type) {
border-bottom: 0
}
.breadcrumb {
background-color: #343a40
}
.breadcrumb-item + .breadcrumb-item::before {
color: #ced4da
}
.breadcrumb-item.active {
color: #ced4da
}
.page-link {
color: #adadad;
background-color: #000;
border: 1px solid #495057
}
.page-link:hover {
color: #878787;
background-color: #343a40;
border-color: #495057
}
.page-link:focus {
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25)
}
.page-item.active .page-link {
color: #000;
background-color: #3395ff;
border-color: #3395ff
}
.page-item.disabled .page-link {
color: #ced4da;
background-color: #000;
border-color: #495057
}
}
@media (prefers-color-scheme:dark) {
.badge-primary {
color: #e9ecef;
background-color: #3395ff
}
a.badge-primary:focus,
a.badge-primary:hover {
color: #e9ecef;
background-color: #007bff
}
a.badge-primary.focus,
a.badge-primary:focus {
box-shadow: 0 0 0 .2rem rgba(51,149,255,.5)
}
.badge-secondary {
color: #e9ecef;
background-color: #6c757d
}
a.badge-secondary:focus,
a.badge-secondary:hover {
color: #e9ecef;
background-color: #545b62
}
a.badge-secondary.focus,
a.badge-secondary:focus {
box-shadow: 0 0 0 .2rem rgba(108,117,125,.5)
}
.badge-success {
color: #e9ecef;
background-color: #28a745
}
a.badge-success:focus,
a.badge-success:hover {
color: #e9ecef;
background-color: #1e7e34
}
a.badge-success.focus,
a.badge-success:focus {
box-shadow: 0 0 0 .2rem rgba(40,167,69,.5)
}
.badge-info {
color: #e9ecef;
background-color: #17a2b8
}
a.badge-info:focus,
a.badge-info:hover {
color: #e9ecef;
background-color: #117a8b
}
a.badge-info.focus,
a.badge-info:focus {
box-shadow: 0 0 0 .2rem rgba(23,162,184,.5)
}
.badge-warning {
color: #343a40;
background-color: #fd7e14
}
a.badge-warning:focus,
a.badge-warning:hover {
color: #343a40;
background-color: #dc6502
}
a.badge-warning.focus,
a.badge-warning:focus {
box-shadow: 0 0 0 .2rem rgba(253,126,20,.5)
}
.badge-danger {
color: #e9ecef;
background-color: #dc3545
}
a.badge-danger:focus,
a.badge-danger:hover {
color: #e9ecef;
background-color: #bd2130
}
a.badge-danger.focus,
a.badge-danger:focus {
box-shadow: 0 0 0 .2rem rgba(220,53,69,.5)
}
.badge-light {
color: #343a40;
background-color: #dee2e6
}
a.badge-light:focus,
a.badge-light:hover {
color: #343a40;
background-color: #c1c9d0
}
a.badge-light.focus,
a.badge-light:focus {
box-shadow: 0 0 0 .2rem rgba(222,226,230,.5)
}
.badge-dark {
color: #e9ecef;
background-color: #343a40
}
a.badge-dark:focus,
a.badge-dark:hover {
color: #e9ecef;
background-color: #1d2124
}
a.badge-dark.focus,
a.badge-dark:focus {
box-shadow: 0 0 0 .2rem rgba(52,58,64,.5)
}
.jumbotron {
background-color: #343a40
}
}
@media (prefers-color-scheme:dark) {
.alert {
border: 1px solid transparent
}
.alert-heading {
color: inherit
}
.alert-dismissible .close {
color: inherit
}
.alert-primary {
color: #1b4e85;
background-color: #d6eaff;
border-color: #c6e1ff
}
.alert-primary .alert-link {
color: #12355b
}
.alert-secondary {
color: #383d41;
background-color: #e2e3e5;
border-color: #d6d8db
}
.alert-secondary .alert-link {
color: #202326
}
.alert-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb
}
.alert-success .alert-link {
color: #0b2e13
}
.alert-info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb
}
.alert-info .alert-link {
color: #062c33
}
.alert-warning {
color: #84420a;
background-color: #ffe5d0;
border-color: #fedbbd
}
.alert-warning .alert-link {
color: #552a06
}
.alert-danger {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb
}
.alert-danger .alert-link {
color: #491217
}
.alert-light,
.alert-themed-inverted {
color: #737678;
background-color: #f8f9fa;
border-color: #f6f7f8
}
.alert-light .alert-link,
.alert-themed-inverted .alert-link {
color: #5a5c5e
}
.alert-dark,
.alert-themed {
color: #1b1e21;
background-color: #d6d8d9;
border-color: #c6c8ca
}
.alert-dark .alert-link,
.alert-themed .alert-link {
color: #040505
}
@-webkit-keyframes progress-bar-stripes {
from {
background-position: 1rem 0
}
to {
background-position: 0 0
}
}
@keyframes progress-bar-stripes {
from {
background-position: 1rem 0
}
to {
background-position: 0 0
}
}
.progress {
background-color: #e9ecef
}
.progress-bar {
color: #fff;
background-color: #007bff
}
}
@media (prefers-color-scheme:dark) {
.progress-bar-striped {
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
}
}
@media (prefers-color-scheme:dark) {
.list-group-item-action {
color: #dee2e6
}
.list-group-item-action:focus,
.list-group-item-action:hover {
color: #dee2e6;
background-color: #212529
}
.list-group-item-action:active {
color: #d3d3d3;
background-color: #343a40
}
.list-group-item {
background-color: rgba(25,29,33,.05);
border: 1px solid rgba(255,255,255,.125)
}
.list-group-item.disabled,
.list-group-item:disabled {
color: #ced4da;
background-color: rgba(25,29,33,.05)
}
.list-group-item.active {
color: #000;
background-color: #3395ff;
border-color: #3395ff
}
}
@media (prefers-color-scheme:dark) {
.list-group-item-primary {
color: #1b4e85;
background-color: #c6e1ff
}
.list-group-item-primary.list-group-item-action:focus,
.list-group-item-primary.list-group-item-action:hover {
color: #1b4e85;
background-color: #add4ff
}
.list-group-item-primary.list-group-item-action.active {
color: #fff;
background-color: #1b4e85;
border-color: #1b4e85
}
.list-group-item-secondary {
color: #383d41;
background-color: #d6d8db
}
.list-group-item-secondary.list-group-item-action:focus,
.list-group-item-secondary.list-group-item-action:hover {
color: #383d41;
background-color: #c8cbcf
}
.list-group-item-secondary.list-group-item-action.active {
color: #fff;
background-color: #383d41;
border-color: #383d41
}
.list-group-item-success {
color: #155724;
background-color: #c3e6cb
}
.list-group-item-success.list-group-item-action:focus,
.list-group-item-success.list-group-item-action:hover {
color: #155724;
background-color: #b1dfbb
}
.list-group-item-success.list-group-item-action.active {
color: #fff;
background-color: #155724;
border-color: #155724
}
.list-group-item-info {
color: #0c5460;
background-color: #bee5eb
}
.list-group-item-info.list-group-item-action:focus,
.list-group-item-info.list-group-item-action:hover {
color: #0c5460;
background-color: #abdde5
}
.list-group-item-info.list-group-item-action.active {
color: #fff;
background-color: #0c5460;
border-color: #0c5460
}
.list-group-item-warning {
color: #84420a;
background-color: #fedbbd
}
.list-group-item-warning.list-group-item-action:focus,
.list-group-item-warning.list-group-item-action:hover {
color: #84420a;
background-color: #fecda4
}
.list-group-item-warning.list-group-item-action.active {
color: #fff;
background-color: #84420a;
border-color: #84420a
}
.list-group-item-danger {
color: #721c24;
background-color: #f5c6cb
}
.list-group-item-danger.list-group-item-action:focus,
.list-group-item-danger.list-group-item-action:hover {
color: #721c24;
background-color: #f1b0b7
}
.list-group-item-danger.list-group-item-action.active {
color: #fff;
background-color: #721c24;
border-color: #721c24
}
.list-group-item-light {
color: #737678;
background-color: #f6f7f8
}
.list-group-item-light.list-group-item-action:focus,
.list-group-item-light.list-group-item-action:hover {
color: #737678;
background-color: #e8eaed
}
.list-group-item-light.list-group-item-action.active {
color: #fff;
background-color: #737678;
border-color: #737678
}
.list-group-item-dark {
color: #1b1e21;
background-color: #c6c8ca
}
.list-group-item-dark.list-group-item-action:focus,
.list-group-item-dark.list-group-item-action:hover {
color: #1b1e21;
background-color: #b9bbbe
}
.list-group-item-dark.list-group-item-action.active {
color: #fff;
background-color: #1b1e21;
border-color: #1b1e21
}
.close {
color: #fff;
text-shadow: 0 1px 0 #000
}
.close:hover {
color: #fff
}
button.close {
background-color: transparent;
border: 0
}
.toast {
background-color: rgba(0,0,0,.85);
border: 1px solid rgba(255,255,255,.1);
box-shadow: 0 .25rem .75rem rgba(255,255,255,.1)
}
.toast-header {
color: #ced4da;
background-color: rgba(0,0,0,.85);
border-bottom: 1px solid rgba(255,255,255,.05)
}
}
@media (prefers-color-scheme:dark) {
.modal-content {
background-color: #191d21;
border: 1px solid rgba(255,255,255,.2)
}
.modal-backdrop {
background-color: #000
}
.modal-header {
border-bottom: 1px solid #343a40
}
.modal-footer {
border-top: 1px solid #343a40
}
}
@media (prefers-color-scheme:dark) {
.tooltip {
text-shadow: none
}
.tooltip .arrow::before {
border-color: transparent
}
.tooltip-inner {
color: #fff;
background-color: #000
}
.popover {
text-shadow: none;
background-color: #fff;
border: 1px solid rgba(0,0,0,.2)
}
.popover .arrow::after,
.popover .arrow::before {
border-color: transparent
}
.bs-popover-auto[x-placement^=bottom] .popover-header::before,
.bs-popover-bottom .popover-header::before {
border-bottom: 1px solid #f7f7f7
}
.popover-header {
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb
}
.popover-body {
color: #212529
}
}
@media (prefers-color-scheme:dark) {
.carousel-control-next,
.carousel-control-prev {
color: #fff;
background: 0 0;
border: 0
}
}
@media (prefers-color-scheme:dark) {
.carousel-control-next:focus,
.carousel-control-next:hover,
.carousel-control-prev:focus,
.carousel-control-prev:hover {
color: #fff
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
background: 50%/100% 100% no-repeat
}
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")
}
.carousel-indicators li {
background-color: #fff;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent
}
}
@media (prefers-color-scheme:dark) {
.carousel-caption {
color: #fff
}
@-webkit-keyframes spinner-border {
to {
transform: rotate(360deg)
}
}
@keyframes spinner-border {
to {
transform: rotate(360deg)
}
}
.spinner-border {
border: .25em solid currentcolor
}
@-webkit-keyframes spinner-grow {
0% {
transform: scale(0)
}
50% {
opacity: 1;
transform: none
}
}
@keyframes spinner-grow {
0% {
transform: scale(0)
}
50% {
opacity: 1;
transform: none
}
}
.spinner-grow {
background-color: currentcolor
}
}
@media (prefers-color-scheme:dark) {
.bg-primary {
background-color: #3395ff!important
}
a.bg-primary:focus,
a.bg-primary:hover,
button.bg-primary:focus,
button.bg-primary:hover {
background-color: #007bff!important
}
.bg-secondary {
background-color: #6c757d!important
}
a.bg-secondary:focus,
a.bg-secondary:hover,
button.bg-secondary:focus,
button.bg-secondary:hover {
background-color: #545b62!important
}
.bg-success {
background-color: #28a745!important
}
a.bg-success:focus,
a.bg-success:hover,
button.bg-success:focus,
button.bg-success:hover {
background-color: #1e7e34!important
}
.bg-info {
background-color: #17a2b8!important
}
a.bg-info:focus,
a.bg-info:hover,
button.bg-info:focus,
button.bg-info:hover {
background-color: #117a8b!important
}
.bg-warning {
background-color: #fd7e14!important
}
a.bg-warning:focus,
a.bg-warning:hover,
button.bg-warning:focus,
button.bg-warning:hover {
background-color: #dc6502!important
}
.bg-danger {
background-color: #dc3545!important
}
a.bg-danger:focus,
a.bg-danger:hover,
button.bg-danger:focus,
button.bg-danger:hover {
background-color: #bd2130!important
}
.bg-light,
.bg-themed-inverted {
background-color: #dee2e6!important
}
a.bg-light:focus,
a.bg-light:hover,
a.bg-themed-inverted:focus,
a.bg-themed-inverted:hover,
button.bg-light:focus,
button.bg-light:hover,
button.bg-themed-inverted:focus,
button.bg-themed-inverted:hover {
background-color: #c1c9d0!important
}
.bg-dark,
.bg-themed,
.navbar-themed {
background-color: #343a40!important
}
a.bg-dark:focus,
a.bg-dark:hover,
a.bg-themed:focus,
a.bg-themed:hover,
a.navbar-themed:focus,
a.navbar-themed:hover,
button.bg-dark:focus,
button.bg-dark:hover,
button.bg-themed:focus,
button.bg-themed:hover,
button.navbar-themed:focus,
button.navbar-themed:hover {
background-color: #1d2124!important
}
.bg-white {
background-color: #fff!important
}
.bg-transparent {
background-color: transparent!important
}
.border {
border: 1px solid #343a40!important
}
.border-top {
border-top: 1px solid #343a40!important
}
.border-right {
border-right: 1px solid #343a40!important
}
.border-bottom {
border-bottom: 1px solid #343a40!important
}
.border-left {
border-left: 1px solid #343a40!important
}
.border-0 {
border: 0!important
}
.border-top-0 {
border-top: 0!important
}
.border-right-0 {
border-right: 0!important
}
.border-bottom-0 {
border-bottom: 0!important
}
.border-left-0 {
border-left: 0!important
}
.border-primary {
border-color: #3395ff!important
}
.border-secondary {
border-color: #6c757d!important
}
.border-success {
border-color: #28a745!important
}
.border-info {
border-color: #17a2b8!important
}
.border-warning {
border-color: #fd7e14!important
}
.border-danger {
border-color: #dc3545!important
}
.border-light {
border-color: #dee2e6!important
}
.border-dark {
border-color: #343a40!important
}
.border-white {
border-color: #fff!important
}
}
@media (prefers-color-scheme:dark) {
.embed-responsive .embed-responsive-item,
.embed-responsive embed,
.embed-responsive iframe,
.embed-responsive object,
.embed-responsive video {
border: 0
}
}
@media (prefers-color-scheme:dark) {
@supports ((position:-webkit-sticky) or (position:sticky)) {
.sticky-top {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 1020
}
}
.sr-only {
border: 0
}
.shadow-sm {
box-shadow: 0 .125rem .25rem rgba(0,0,0,.075)!important
}
.shadow {
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important
}
.shadow-lg {
box-shadow: 0 1rem 3rem rgba(0,0,0,.175)!important
}
.shadow-none {
box-shadow: none!important
}
}
@media (prefers-color-scheme:dark) {
.stretched-link::after {
background-color: rgba(0,0,0,0)
}
}
@media (prefers-color-scheme:dark) {
.text-white {
color: #fff!important
}
.text-primary {
color: #3395ff!important
}
a.text-primary:focus,
a.text-primary:hover {
color: #006fe6!important
}
.text-secondary {
color: #6c757d!important
}
a.text-secondary:focus,
a.text-secondary:hover {
color: #494f54!important
}
.text-success {
color: #28a745!important
}
a.text-success:focus,
a.text-success:hover {
color: #19692c!important
}
.text-info {
color: #17a2b8!important
}
a.text-info:focus,
a.text-info:hover {
color: #0f6674!important
}
.text-warning {
color: #fd7e14!important
}
a.text-warning:focus,
a.text-warning:hover {
color: #c35a02!important
}
.text-danger {
color: #dc3545!important
}
a.text-danger:focus,
a.text-danger:hover {
color: #a71d2a!important
}
.text-light,
.text-themed {
color: #dee2e6!important
}
a.text-light:focus,
a.text-light:hover,
a.text-themed:focus,
a.text-themed:hover {
color: #b2bcc5!important
}
.text-dark,
.text-themed-inverted {
color: #343a40!important
}
a.text-dark:focus,
a.text-dark:hover,
a.text-themed-inverted:focus,
a.text-themed-inverted:hover {
color: #121416!important
}
.text-body {
color: #d3d3d3!important
}
.text-muted {
color: #6c757d!important
}
.text-black-50 {
color: rgba(0,0,0,.5)!important
}
.text-white-50 {
color: rgba(255,255,255,.5)!important
}
.text-hide {
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0
}
.text-reset {
color: inherit!important
}
}
\ No newline at end of file
/*!
* Bootstrap-Dark v4.0.0 (https://github.com/ForEvolve/bootstrap-dark)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@media (prefers-color-scheme:dark) {body {color: #d3d3d3;background-color: #191d21 }abbr[data-original-title], abbr[title] {border-bottom: 0 }a {color: #adadad;background-color: transparent }a:hover {color: #878787 }a:not([href]):not([class]) {color: inherit }a:not([href]):not([class]):hover {color: inherit }caption {color: #6c757d }fieldset {border: 0 }legend {color: inherit }hr {border: 0;border-top: 1px solid rgba(255,255,255,.1) }.mark, mark {background-color: #fcf8e3 }.blockquote-footer {color: #6c757d }.img-thumbnail {background-color: #fff;border: 1px solid #dee2e6 }.figure-caption {color: #6c757d }code {color: #e83e8c }a > code {color: inherit }kbd {color: #fff;background-color: #212529 }pre {color: #f8f9fa }pre code {color: inherit }}@media (prefers-color-scheme:dark) {.table {color: #d3d3d3 }.table td, .table th {border-top: 1px solid #343a40 }.table thead th {border-bottom: 2px solid #343a40 }.table tbody + tbody {border-top: 2px solid #343a40 }.table-bordered {border: 1px solid #343a40 }.table-bordered td, .table-bordered th {border: 1px solid #343a40 }.table-borderless tbody + tbody, .table-borderless td, .table-borderless th, .table-borderless thead th {border: 0 }.table-striped tbody tr:nth-of-type(odd) {background-color: rgba(0,0,0,.05) }.table-hover tbody tr:hover {color: #d3d3d3;background-color: rgba(0,0,0,.075) }.table-primary, .table-primary > td, .table-primary > th {background-color: #c6e1ff }.table-primary tbody + tbody, .table-primary td, .table-primary th, .table-primary thead th {border-color: #95c8ff }.table-hover .table-primary:hover {background-color: #add4ff }.table-hover .table-primary:hover > td, .table-hover .table-primary:hover > th {background-color: #add4ff }.table-secondary, .table-secondary > td, .table-secondary > th {background-color: #d6d8db }.table-secondary tbody + tbody, .table-secondary td, .table-secondary th, .table-secondary thead th {border-color: #b3b7bb }.table-hover .table-secondary:hover {background-color: #c8cbcf }.table-hover .table-secondary:hover > td, .table-hover .table-secondary:hover > th {background-color: #c8cbcf }.table-success, .table-success > td, .table-success > th {background-color: #c3e6cb }.table-success tbody + tbody, .table-success td, .table-success th, .table-success thead th {border-color: #8fd19e }.table-hover .table-success:hover {background-color: #b1dfbb }.table-hover .table-success:hover > td, .table-hover .table-success:hover > th {background-color: #b1dfbb }.table-info, .table-info > td, .table-info > th {background-color: #bee5eb }.table-info tbody + tbody, .table-info td, .table-info th, .table-info thead th {border-color: #86cfda }.table-hover .table-info:hover {background-color: #abdde5 }.table-hover .table-info:hover > td, .table-hover .table-info:hover > th {background-color: #abdde5 }.table-warning, .table-warning > td, .table-warning > th {background-color: #fedbbd }.table-warning tbody + tbody, .table-warning td, .table-warning th, .table-warning thead th {border-color: #febc85 }.table-hover .table-warning:hover {background-color: #fecda4 }.table-hover .table-warning:hover > td, .table-hover .table-warning:hover > th {background-color: #fecda4 }.table-danger, .table-danger > td, .table-danger > th {background-color: #f5c6cb }.table-danger tbody + tbody, .table-danger td, .table-danger th, .table-danger thead th {border-color: #ed969e }.table-hover .table-danger:hover {background-color: #f1b0b7 }.table-hover .table-danger:hover > td, .table-hover .table-danger:hover > th {background-color: #f1b0b7 }.table-light, .table-light > td, .table-light > th {background-color: #f6f7f8 }.table-light tbody + tbody, .table-light td, .table-light th, .table-light thead th {border-color: #eef0f2 }.table-hover .table-light:hover {background-color: #e8eaed }.table-hover .table-light:hover > td, .table-hover .table-light:hover > th {background-color: #e8eaed }.table-dark, .table-dark > td, .table-dark > th {background-color: #c6c8ca }.table-dark tbody + tbody, .table-dark td, .table-dark th, .table-dark thead th {border-color: #95999c }.table-hover .table-dark:hover {background-color: #b9bbbe }.table-hover .table-dark:hover > td, .table-hover .table-dark:hover > th {background-color: #b9bbbe }.table-active, .table-active > td, .table-active > th {background-color: rgba(0,0,0,.075) }.table-hover .table-active:hover {background-color: rgba(0,0,0,.075) }.table-hover .table-active:hover > td, .table-hover .table-active:hover > th {background-color: rgba(0,0,0,.075) }.table .thead-dark th {color: #dee2e6;background-color: #343a40;border-color: #454d55 }.table .thead-light th {color: #495057;background-color: #e9ecef;border-color: #343a40 }.table-dark {color: #dee2e6;background-color: #343a40 }.table-dark td, .table-dark th, .table-dark thead th {border-color: #454d55 }.table-dark.table-bordered {border: 0 }.table-dark.table-striped tbody tr:nth-of-type(odd) {background-color: rgba(255,255,255,.05) }.table-dark.table-hover tbody tr:hover {color: #fff;background-color: rgba(255,255,255,.075) }}@media (prefers-color-scheme:dark) and (max-width:575.98px) {.table-responsive-sm > .table-bordered {border: 0 }}@media (prefers-color-scheme:dark) and (max-width:767.98px) {.table-responsive-md > .table-bordered {border: 0 }}@media (prefers-color-scheme:dark) and (max-width:991.98px) {.table-responsive-lg > .table-bordered {border: 0 }}@media (prefers-color-scheme:dark) and (max-width:1199.98px) {.table-responsive-xl > .table-bordered {border: 0 }}@media (prefers-color-scheme:dark) {.table-responsive > .table-bordered {border: 0 }.table-primary, .table-primary > td, .table-primary > th {color: #343a40 }.table-hover .table-primary:hover {color: #343a40 }.table-hover .table-primary:hover > td, .table-hover .table-primary:hover > th {color: #343a40 }.table-secondary, .table-secondary > td, .table-secondary > th {color: #343a40 }.table-hover .table-secondary:hover {color: #343a40 }.table-hover .table-secondary:hover > td, .table-hover .table-secondary:hover > th {color: #343a40 }.table-success, .table-success > td, .table-success > th {color: #343a40 }.table-hover .table-success:hover {color: #343a40 }.table-hover .table-success:hover > td, .table-hover .table-success:hover > th {color: #343a40 }.table-info, .table-info > td, .table-info > th {color: #343a40 }.table-hover .table-info:hover {color: #343a40 }.table-hover .table-info:hover > td, .table-hover .table-info:hover > th {color: #343a40 }.table-warning, .table-warning > td, .table-warning > th {color: #343a40 }.table-hover .table-warning:hover {color: #343a40 }.table-hover .table-warning:hover > td, .table-hover .table-warning:hover > th {color: #343a40 }.table-danger, .table-danger > td, .table-danger > th {color: #343a40 }.table-hover .table-danger:hover {color: #343a40 }.table-hover .table-danger:hover > td, .table-hover .table-danger:hover > th {color: #343a40 }.table-light, .table-light > td, .table-light > th {color: #343a40 }.table-hover .table-light:hover {color: #343a40 }.table-hover .table-light:hover > td, .table-hover .table-light:hover > th {color: #343a40 }.table-dark, .table-dark > td, .table-dark > th {color: #343a40 }.table-hover .table-dark:hover {color: #343a40 }.table-hover .table-dark:hover > td, .table-hover .table-dark:hover > th {color: #343a40 }.table-active, .table-active > td, .table-active > th {color: #e9ecef }.table-hover .table-active:hover {color: #e9ecef }.table-hover .table-active:hover > td, .table-hover .table-active:hover > th {color: #e9ecef }.table-dark {color: #dee2e6 }.form-control {color: #dee2e6;background-color: #000;border: 1px solid #6c757d }}@media (prefers-color-scheme:dark) {.form-control::-ms-expand {background-color: transparent;border: 0 }.form-control:focus {color: #dee2e6;background-color: #191d21;border-color: #b3d7ff;box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.form-control::-webkit-input-placeholder {color: #6c757d }.form-control::-moz-placeholder {color: #6c757d }.form-control::-ms-input-placeholder {color: #6c757d }.form-control::placeholder {color: #6c757d }.form-control:disabled, .form-control[readonly] {background-color: #343a40 }select.form-control:-moz-focusring {color: transparent;text-shadow: 0 0 0 #dee2e6 }select.form-control:focus::-ms-value {color: #dee2e6;background-color: #000 }.form-control-plaintext {color: #212529;background-color: transparent;border: solid transparent }.form-check-input:disabled ~ .form-check-label, .form-check-input[disabled] ~ .form-check-label {color: #6c757d }.valid-feedback {color: #28a745 }.valid-tooltip {color: #e9ecef;background-color: rgba(40,167,69,.9) }.form-control.is-valid, .was-validated .form-control:valid {border-color: #28a745;background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") }.form-control.is-valid:focus, .was-validated .form-control:valid:focus {border-color: #28a745;box-shadow: 0 0 0 .2rem rgba(40,167,69,.25) }.custom-select.is-valid, .was-validated .custom-select:valid {border-color: #28a745;background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat }.custom-select.is-valid:focus, .was-validated .custom-select:valid:focus {border-color: #28a745;box-shadow: 0 0 0 .2rem rgba(40,167,69,.25) }.form-check-input.is-valid ~ .form-check-label, .was-validated .form-check-input:valid ~ .form-check-label {color: #28a745 }.custom-control-input.is-valid ~ .custom-control-label, .was-validated .custom-control-input:valid ~ .custom-control-label {color: #28a745 }.custom-control-input.is-valid ~ .custom-control-label::before, .was-validated .custom-control-input:valid ~ .custom-control-label::before {border-color: #28a745 }.custom-control-input.is-valid:checked ~ .custom-control-label::before, .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before {border-color: #34ce57;background-color: #34ce57 }.custom-control-input.is-valid:focus ~ .custom-control-label::before, .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before {box-shadow: 0 0 0 .2rem rgba(40,167,69,.25) }.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before, .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before {border-color: #28a745 }.custom-file-input.is-valid ~ .custom-file-label, .was-validated .custom-file-input:valid ~ .custom-file-label {border-color: #28a745 }.custom-file-input.is-valid:focus ~ .custom-file-label, .was-validated .custom-file-input:valid:focus ~ .custom-file-label {border-color: #28a745;box-shadow: 0 0 0 .2rem rgba(40,167,69,.25) }.invalid-feedback {color: #dc3545 }.invalid-tooltip {color: #e9ecef;background-color: rgba(220,53,69,.9) }.form-control.is-invalid, .was-validated .form-control:invalid {border-color: #dc3545;background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") }.form-control.is-invalid:focus, .was-validated .form-control:invalid:focus {border-color: #dc3545;box-shadow: 0 0 0 .2rem rgba(220,53,69,.25) }.custom-select.is-invalid, .was-validated .custom-select:invalid {border-color: #dc3545;background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat }.custom-select.is-invalid:focus, .was-validated .custom-select:invalid:focus {border-color: #dc3545;box-shadow: 0 0 0 .2rem rgba(220,53,69,.25) }.form-check-input.is-invalid ~ .form-check-label, .was-validated .form-check-input:invalid ~ .form-check-label {color: #dc3545 }.custom-control-input.is-invalid ~ .custom-control-label, .was-validated .custom-control-input:invalid ~ .custom-control-label {color: #dc3545 }.custom-control-input.is-invalid ~ .custom-control-label::before, .was-validated .custom-control-input:invalid ~ .custom-control-label::before {border-color: #dc3545 }.custom-control-input.is-invalid:checked ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before {border-color: #e4606d;background-color: #e4606d }.custom-control-input.is-invalid:focus ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before {box-shadow: 0 0 0 .2rem rgba(220,53,69,.25) }.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before {border-color: #dc3545 }.custom-file-input.is-invalid ~ .custom-file-label, .was-validated .custom-file-input:invalid ~ .custom-file-label {border-color: #dc3545 }.custom-file-input.is-invalid:focus ~ .custom-file-label, .was-validated .custom-file-input:invalid:focus ~ .custom-file-label {border-color: #dc3545;box-shadow: 0 0 0 .2rem rgba(220,53,69,.25) }}@media (prefers-color-scheme:dark) {.btn {color: #d3d3d3;background-color: transparent;border: 1px solid transparent }}@media (prefers-color-scheme:dark) {.btn:hover {color: #d3d3d3 }.btn.focus, .btn:focus {box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.btn-primary {color: #e9ecef;background-color: #3395ff;border-color: #3395ff }.btn-primary:hover {color: #e9ecef;background-color: #0d82ff;border-color: #007bff }.btn-primary.focus, .btn-primary:focus {color: #e9ecef;background-color: #0d82ff;border-color: #007bff;box-shadow: 0 0 0 .2rem rgba(78,162,253,.5) }.btn-primary.disabled, .btn-primary:disabled {color: #e9ecef;background-color: #3395ff;border-color: #3395ff }.btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled):active, .show > .btn-primary.dropdown-toggle {color: #e9ecef;background-color: #007bff;border-color: #0075f2 }.btn-primary:not(:disabled):not(.disabled).active:focus, .btn-primary:not(:disabled):not(.disabled):active:focus, .show > .btn-primary.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(78,162,253,.5) }.btn-secondary {color: #e9ecef;background-color: #6c757d;border-color: #6c757d }.btn-secondary:hover {color: #e9ecef;background-color: #5a6268;border-color: #545b62 }.btn-secondary.focus, .btn-secondary:focus {color: #e9ecef;background-color: #5a6268;border-color: #545b62;box-shadow: 0 0 0 .2rem rgba(127,135,142,.5) }.btn-secondary.disabled, .btn-secondary:disabled {color: #e9ecef;background-color: #6c757d;border-color: #6c757d }.btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show > .btn-secondary.dropdown-toggle {color: #e9ecef;background-color: #545b62;border-color: #4e555b }.btn-secondary:not(:disabled):not(.disabled).active:focus, .btn-secondary:not(:disabled):not(.disabled):active:focus, .show > .btn-secondary.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(127,135,142,.5) }.btn-success {color: #e9ecef;background-color: #28a745;border-color: #28a745 }.btn-success:hover {color: #e9ecef;background-color: #218838;border-color: #1e7e34 }.btn-success.focus, .btn-success:focus {color: #e9ecef;background-color: #218838;border-color: #1e7e34;box-shadow: 0 0 0 .2rem rgba(69,177,95,.5) }.btn-success.disabled, .btn-success:disabled {color: #e9ecef;background-color: #28a745;border-color: #28a745 }.btn-success:not(:disabled):not(.disabled).active, .btn-success:not(:disabled):not(.disabled):active, .show > .btn-success.dropdown-toggle {color: #e9ecef;background-color: #1e7e34;border-color: #1c7430 }.btn-success:not(:disabled):not(.disabled).active:focus, .btn-success:not(:disabled):not(.disabled):active:focus, .show > .btn-success.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(69,177,95,.5) }.btn-info {color: #e9ecef;background-color: #17a2b8;border-color: #17a2b8 }.btn-info:hover {color: #e9ecef;background-color: #138496;border-color: #117a8b }.btn-info.focus, .btn-info:focus {color: #e9ecef;background-color: #138496;border-color: #117a8b;box-shadow: 0 0 0 .2rem rgba(55,173,192,.5) }.btn-info.disabled, .btn-info:disabled {color: #e9ecef;background-color: #17a2b8;border-color: #17a2b8 }.btn-info:not(:disabled):not(.disabled).active, .btn-info:not(:disabled):not(.disabled):active, .show > .btn-info.dropdown-toggle {color: #e9ecef;background-color: #117a8b;border-color: #10707f }.btn-info:not(:disabled):not(.disabled).active:focus, .btn-info:not(:disabled):not(.disabled):active:focus, .show > .btn-info.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(55,173,192,.5) }.btn-warning {color: #343a40;background-color: #fd7e14;border-color: #fd7e14 }.btn-warning:hover {color: #e9ecef;background-color: #e96b02;border-color: #dc6502 }.btn-warning.focus, .btn-warning:focus {color: #e9ecef;background-color: #e96b02;border-color: #dc6502;box-shadow: 0 0 0 .2rem rgba(223,116,27,.5) }.btn-warning.disabled, .btn-warning:disabled {color: #343a40;background-color: #fd7e14;border-color: #fd7e14 }.btn-warning:not(:disabled):not(.disabled).active, .btn-warning:not(:disabled):not(.disabled):active, .show > .btn-warning.dropdown-toggle {color: #e9ecef;background-color: #dc6502;border-color: #cf5f02 }.btn-warning:not(:disabled):not(.disabled).active:focus, .btn-warning:not(:disabled):not(.disabled):active:focus, .show > .btn-warning.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(223,116,27,.5) }.btn-danger {color: #e9ecef;background-color: #dc3545;border-color: #dc3545 }.btn-danger:hover {color: #e9ecef;background-color: #c82333;border-color: #bd2130 }.btn-danger.focus, .btn-danger:focus {color: #e9ecef;background-color: #c82333;border-color: #bd2130;box-shadow: 0 0 0 .2rem rgba(222,80,95,.5) }.btn-danger.disabled, .btn-danger:disabled {color: #e9ecef;background-color: #dc3545;border-color: #dc3545 }.btn-danger:not(:disabled):not(.disabled).active, .btn-danger:not(:disabled):not(.disabled):active, .show > .btn-danger.dropdown-toggle {color: #e9ecef;background-color: #bd2130;border-color: #b21f2d }.btn-danger:not(:disabled):not(.disabled).active:focus, .btn-danger:not(:disabled):not(.disabled):active:focus, .show > .btn-danger.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(222,80,95,.5) }.btn-light {color: #343a40;background-color: #dee2e6;border-color: #dee2e6 }.btn-light:hover {color: #343a40;background-color: #c8cfd6;border-color: #c1c9d0 }.btn-light.focus, .btn-light:focus {color: #343a40;background-color: #c8cfd6;border-color: #c1c9d0;box-shadow: 0 0 0 .2rem rgba(197,201,205,.5) }.btn-light.disabled, .btn-light:disabled {color: #343a40;background-color: #dee2e6;border-color: #dee2e6 }.btn-light:not(:disabled):not(.disabled).active, .btn-light:not(:disabled):not(.disabled):active, .show > .btn-light.dropdown-toggle {color: #343a40;background-color: #c1c9d0;border-color: #bac2cb }.btn-light:not(:disabled):not(.disabled).active:focus, .btn-light:not(:disabled):not(.disabled):active:focus, .show > .btn-light.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(197,201,205,.5) }.btn-dark {color: #e9ecef;background-color: #343a40;border-color: #343a40 }.btn-dark:hover {color: #e9ecef;background-color: #23272b;border-color: #1d2124 }.btn-dark.focus, .btn-dark:focus {color: #e9ecef;background-color: #23272b;border-color: #1d2124;box-shadow: 0 0 0 .2rem rgba(79,85,90,.5) }.btn-dark.disabled, .btn-dark:disabled {color: #e9ecef;background-color: #343a40;border-color: #343a40 }.btn-dark:not(:disabled):not(.disabled).active, .btn-dark:not(:disabled):not(.disabled):active, .show > .btn-dark.dropdown-toggle {color: #e9ecef;background-color: #1d2124;border-color: #171a1d }.btn-dark:not(:disabled):not(.disabled).active:focus, .btn-dark:not(:disabled):not(.disabled):active:focus, .show > .btn-dark.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(79,85,90,.5) }.btn-outline-primary {color: #3395ff;border-color: #3395ff }.btn-outline-primary:hover {color: #e9ecef;background-color: #3395ff;border-color: #3395ff }.btn-outline-primary.focus, .btn-outline-primary:focus {box-shadow: 0 0 0 .2rem rgba(51,149,255,.5) }.btn-outline-primary.disabled, .btn-outline-primary:disabled {color: #3395ff;background-color: transparent }.btn-outline-primary:not(:disabled):not(.disabled).active, .btn-outline-primary:not(:disabled):not(.disabled):active, .show > .btn-outline-primary.dropdown-toggle {color: #e9ecef;background-color: #3395ff;border-color: #3395ff }.btn-outline-primary:not(:disabled):not(.disabled).active:focus, .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-primary.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(51,149,255,.5) }.btn-outline-secondary {color: #6c757d;border-color: #6c757d }.btn-outline-secondary:hover {color: #e9ecef;background-color: #6c757d;border-color: #6c757d }.btn-outline-secondary.focus, .btn-outline-secondary:focus {box-shadow: 0 0 0 .2rem rgba(108,117,125,.5) }.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {color: #6c757d;background-color: transparent }.btn-outline-secondary:not(:disabled):not(.disabled).active, .btn-outline-secondary:not(:disabled):not(.disabled):active, .show > .btn-outline-secondary.dropdown-toggle {color: #e9ecef;background-color: #6c757d;border-color: #6c757d }.btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(108,117,125,.5) }.btn-outline-success {color: #28a745;border-color: #28a745 }.btn-outline-success:hover {color: #e9ecef;background-color: #28a745;border-color: #28a745 }.btn-outline-success.focus, .btn-outline-success:focus {box-shadow: 0 0 0 .2rem rgba(40,167,69,.5) }.btn-outline-success.disabled, .btn-outline-success:disabled {color: #28a745;background-color: transparent }.btn-outline-success:not(:disabled):not(.disabled).active, .btn-outline-success:not(:disabled):not(.disabled):active, .show > .btn-outline-success.dropdown-toggle {color: #e9ecef;background-color: #28a745;border-color: #28a745 }.btn-outline-success:not(:disabled):not(.disabled).active:focus, .btn-outline-success:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-success.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(40,167,69,.5) }.btn-outline-info {color: #17a2b8;border-color: #17a2b8 }.btn-outline-info:hover {color: #e9ecef;background-color: #17a2b8;border-color: #17a2b8 }.btn-outline-info.focus, .btn-outline-info:focus {box-shadow: 0 0 0 .2rem rgba(23,162,184,.5) }.btn-outline-info.disabled, .btn-outline-info:disabled {color: #17a2b8;background-color: transparent }.btn-outline-info:not(:disabled):not(.disabled).active, .btn-outline-info:not(:disabled):not(.disabled):active, .show > .btn-outline-info.dropdown-toggle {color: #e9ecef;background-color: #17a2b8;border-color: #17a2b8 }.btn-outline-info:not(:disabled):not(.disabled).active:focus, .btn-outline-info:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-info.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(23,162,184,.5) }.btn-outline-warning {color: #fd7e14;border-color: #fd7e14 }.btn-outline-warning:hover {color: #343a40;background-color: #fd7e14;border-color: #fd7e14 }.btn-outline-warning.focus, .btn-outline-warning:focus {box-shadow: 0 0 0 .2rem rgba(253,126,20,.5) }.btn-outline-warning.disabled, .btn-outline-warning:disabled {color: #fd7e14;background-color: transparent }.btn-outline-warning:not(:disabled):not(.disabled).active, .btn-outline-warning:not(:disabled):not(.disabled):active, .show > .btn-outline-warning.dropdown-toggle {color: #343a40;background-color: #fd7e14;border-color: #fd7e14 }.btn-outline-warning:not(:disabled):not(.disabled).active:focus, .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-warning.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(253,126,20,.5) }.btn-outline-danger {color: #dc3545;border-color: #dc3545 }.btn-outline-danger:hover {color: #e9ecef;background-color: #dc3545;border-color: #dc3545 }.btn-outline-danger.focus, .btn-outline-danger:focus {box-shadow: 0 0 0 .2rem rgba(220,53,69,.5) }.btn-outline-danger.disabled, .btn-outline-danger:disabled {color: #dc3545;background-color: transparent }.btn-outline-danger:not(:disabled):not(.disabled).active, .btn-outline-danger:not(:disabled):not(.disabled):active, .show > .btn-outline-danger.dropdown-toggle {color: #e9ecef;background-color: #dc3545;border-color: #dc3545 }.btn-outline-danger:not(:disabled):not(.disabled).active:focus, .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-danger.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(220,53,69,.5) }.btn-outline-light {color: #dee2e6;border-color: #dee2e6 }.btn-outline-light:hover {color: #343a40;background-color: #dee2e6;border-color: #dee2e6 }.btn-outline-light.focus, .btn-outline-light:focus {box-shadow: 0 0 0 .2rem rgba(222,226,230,.5) }.btn-outline-light.disabled, .btn-outline-light:disabled {color: #dee2e6;background-color: transparent }.btn-outline-light:not(:disabled):not(.disabled).active, .btn-outline-light:not(:disabled):not(.disabled):active, .show > .btn-outline-light.dropdown-toggle {color: #343a40;background-color: #dee2e6;border-color: #dee2e6 }.btn-outline-light:not(:disabled):not(.disabled).active:focus, .btn-outline-light:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-light.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(222,226,230,.5) }.btn-outline-dark {color: #343a40;border-color: #343a40 }.btn-outline-dark:hover {color: #e9ecef;background-color: #343a40;border-color: #343a40 }.btn-outline-dark.focus, .btn-outline-dark:focus {box-shadow: 0 0 0 .2rem rgba(52,58,64,.5) }.btn-outline-dark.disabled, .btn-outline-dark:disabled {color: #343a40;background-color: transparent }.btn-outline-dark:not(:disabled):not(.disabled).active, .btn-outline-dark:not(:disabled):not(.disabled):active, .show > .btn-outline-dark.dropdown-toggle {color: #e9ecef;background-color: #343a40;border-color: #343a40 }.btn-outline-dark:not(:disabled):not(.disabled).active:focus, .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-dark.dropdown-toggle:focus {box-shadow: 0 0 0 .2rem rgba(52,58,64,.5) }.btn-link {color: #adadad }.btn-link:hover {color: #878787 }.btn-link.disabled, .btn-link:disabled {color: #6c757d }}@media (prefers-color-scheme:dark) {.dropdown-toggle::after {border-top: .3em solid;border-right: .3em solid transparent;border-bottom: 0;border-left: .3em solid transparent }.dropdown-menu {color: #d3d3d3;background-color: #000;border: 1px solid rgba(255,255,255,.15) }}@media (prefers-color-scheme:dark) {.dropup .dropdown-toggle::after {border-top: 0;border-right: .3em solid transparent;border-bottom: .3em solid;border-left: .3em solid transparent }.dropright .dropdown-toggle::after {border-top: .3em solid transparent;border-right: 0;border-bottom: .3em solid transparent;border-left: .3em solid }.dropleft .dropdown-toggle::before {border-top: .3em solid transparent;border-right: .3em solid;border-bottom: .3em solid transparent }.dropdown-divider {border-top: 1px solid #343a40 }.dropdown-item {color: #f8f9fa;background-color: transparent;border: 0 }.dropdown-item:focus, .dropdown-item:hover {color: #fff;background-color: #212529 }.dropdown-item.active, .dropdown-item:active {color: #000;background-color: #3395ff }.dropdown-item.disabled, .dropdown-item:disabled {color: #ced4da;background-color: transparent }.dropdown-header {color: #ced4da }.dropdown-item-text {color: #f8f9fa }.input-group-text {color: #dee2e6;background-color: #343a40;border: 1px solid #6c757d }.custom-control-input:checked ~ .custom-control-label::before {color: #fff;border-color: #007bff;background-color: #007bff }.custom-control-input:focus ~ .custom-control-label::before {box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {border-color: #80bdff }.custom-control-input:not(:disabled):active ~ .custom-control-label::before {color: #fff;background-color: #b3d7ff;border-color: #b3d7ff }.custom-control-input:disabled ~ .custom-control-label, .custom-control-input[disabled] ~ .custom-control-label {color: #6c757d }.custom-control-input:disabled ~ .custom-control-label::before, .custom-control-input[disabled] ~ .custom-control-label::before {background-color: #e9ecef }.custom-control-label::before {background-color: #fff;border: 1px solid #adb5bd }.custom-control-label::after {background: 50%/50% 50% no-repeat }.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e") }.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {border-color: #007bff;background-color: #007bff }.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e") }.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {background-color: rgba(0,123,255,.5) }.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {background-color: rgba(0,123,255,.5) }.custom-radio .custom-control-input:checked ~ .custom-control-label::after {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e") }.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {background-color: rgba(0,123,255,.5) }.custom-switch .custom-control-label::after {background-color: #adb5bd }}@media (prefers-color-scheme:dark) {.custom-switch .custom-control-input:checked ~ .custom-control-label::after {background-color: #fff }.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {background-color: rgba(0,123,255,.5) }.custom-select {color: #dee2e6;background: #000 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border: 1px solid #6c757d }.custom-select:focus {border-color: #80bdff;box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.custom-select:focus::-ms-value {color: #dee2e6;background-color: #000 }.custom-select[multiple], .custom-select[size]:not([size="1"]) {background-image: none }.custom-select:disabled {color: #ced4da;background-color: #343a40 }.custom-select:-moz-focusring {color: transparent;text-shadow: 0 0 0 #dee2e6 }.custom-file-input:focus ~ .custom-file-label {border-color: #80bdff;box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.custom-file-input:disabled ~ .custom-file-label, .custom-file-input[disabled] ~ .custom-file-label {background-color: #e9ecef }.custom-file-label {color: #495057;background-color: #fff;border: 1px solid #ced4da }.custom-file-label::after {color: #495057;background-color: #e9ecef;border-left: inherit }.custom-range {background-color: transparent }.custom-range:focus::-webkit-slider-thumb {box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25) }.custom-range:focus::-moz-range-thumb {box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25) }.custom-range:focus::-ms-thumb {box-shadow: 0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25) }.custom-range::-moz-focus-outer {border: 0 }.custom-range::-webkit-slider-thumb {background-color: #007bff;border: 0 }}@media (prefers-color-scheme:dark) {.custom-range::-webkit-slider-thumb:active {background-color: #b3d7ff }.custom-range::-webkit-slider-runnable-track {color: transparent;background-color: #dee2e6;border-color: transparent }.custom-range::-moz-range-thumb {background-color: #007bff;border: 0 }}@media (prefers-color-scheme:dark) {.custom-range::-moz-range-thumb:active {background-color: #b3d7ff }.custom-range::-moz-range-track {color: transparent;background-color: #dee2e6;border-color: transparent }.custom-range::-ms-thumb {background-color: #007bff;border: 0 }}@media (prefers-color-scheme:dark) {.custom-range::-ms-thumb:active {background-color: #b3d7ff }.custom-range::-ms-track {color: transparent;background-color: transparent;border-color: transparent }.custom-range::-ms-fill-lower {background-color: #dee2e6 }.custom-range::-ms-fill-upper {background-color: #dee2e6 }.custom-range:disabled::-webkit-slider-thumb {background-color: #adb5bd }.custom-range:disabled::-moz-range-thumb {background-color: #adb5bd }.custom-range:disabled::-ms-thumb {background-color: #adb5bd }}@media (prefers-color-scheme:dark) {.nav-link.disabled {color: #6c757d }.nav-tabs {border-bottom: 1px solid rgba(255,255,255,.125) }.nav-tabs .nav-link {background-color: transparent;border: 1px solid transparent }.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {border-color: #495057 #495057 rgba(255,255,255,.125) }.nav-tabs .nav-link.disabled {color: #6c757d;background-color: transparent;border-color: transparent }.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active {color: #f8f9fa;background-color: #191d21;border-color: #495057 #495057 #191d21 }.nav-pills .nav-link {background: 0 0;border: 0 }.nav-pills .nav-link.active, .nav-pills .show > .nav-link {color: #fff;background-color: #007bff }.navbar-toggler {background-color: transparent;border: 1px solid transparent }.navbar-toggler-icon {background: 50%/100% 100% no-repeat }}@media (prefers-color-scheme:dark) {.navbar-light .navbar-brand {color: rgba(0,0,0,.9) }.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover {color: rgba(0,0,0,.9) }.navbar-light .navbar-nav .nav-link {color: rgba(0,0,0,.5) }.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover {color: rgba(0,0,0,.7) }.navbar-light .navbar-nav .nav-link.disabled {color: rgba(0,0,0,.3) }.navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.active, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .show > .nav-link {color: rgba(0,0,0,.9) }.navbar-light .navbar-toggler {color: rgba(0,0,0,.5);border-color: rgba(0,0,0,.1) }.navbar-light .navbar-toggler-icon {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") }.navbar-light .navbar-text {color: rgba(0,0,0,.5) }.navbar-light .navbar-text a {color: rgba(0,0,0,.9) }.navbar-light .navbar-text a:focus, .navbar-light .navbar-text a:hover {color: rgba(0,0,0,.9) }.navbar-dark .navbar-brand, .navbar-themed .navbar-brand {color: #fff }.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover, .navbar-themed .navbar-brand:focus, .navbar-themed .navbar-brand:hover {color: #fff }.navbar-dark .navbar-nav .nav-link, .navbar-themed .navbar-nav .nav-link {color: rgba(255,255,255,.5) }.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover, .navbar-themed .navbar-nav .nav-link:focus, .navbar-themed .navbar-nav .nav-link:hover {color: rgba(255,255,255,.75) }.navbar-dark .navbar-nav .nav-link.disabled, .navbar-themed .navbar-nav .nav-link.disabled {color: rgba(255,255,255,.25) }.navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.active, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .show > .nav-link, .navbar-themed .navbar-nav .active > .nav-link, .navbar-themed .navbar-nav .nav-link.active, .navbar-themed .navbar-nav .nav-link.show, .navbar-themed .navbar-nav .show > .nav-link {color: #fff }.navbar-dark .navbar-toggler, .navbar-themed .navbar-toggler {color: rgba(255,255,255,.5);border-color: rgba(255,255,255,.1) }.navbar-dark .navbar-toggler-icon, .navbar-themed .navbar-toggler-icon {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") }.navbar-dark .navbar-text, .navbar-themed .navbar-text {color: rgba(255,255,255,.5) }.navbar-dark .navbar-text a, .navbar-themed .navbar-text a {color: #fff }.navbar-dark .navbar-text a:focus, .navbar-dark .navbar-text a:hover, .navbar-themed .navbar-text a:focus, .navbar-themed .navbar-text a:hover {color: #fff }.card {background-color: #212529;border: 1px solid rgba(255,255,255,.125) }.card > .list-group {border-top: inherit;border-bottom: inherit }.card > .card-header + .list-group, .card > .list-group + .card-footer {border-top: 0 }.card-header {background-color: rgba(255,255,255,.03);border-bottom: 1px solid rgba(255,255,255,.125) }.card-footer {background-color: rgba(255,255,255,.03);border-top: 1px solid rgba(255,255,255,.125) }.card-header-tabs {border-bottom: 0 }}@media (prefers-color-scheme:dark) and (min-width:576px) {.card-group > .card + .card {border-left: 0 }}@media (prefers-color-scheme:dark) {.accordion > .card:not(:last-of-type) {border-bottom: 0 }.breadcrumb {background-color: #343a40 }.breadcrumb-item + .breadcrumb-item::before {color: #ced4da }.breadcrumb-item.active {color: #ced4da }.page-link {color: #adadad;background-color: #000;border: 1px solid #495057 }.page-link:hover {color: #878787;background-color: #343a40;border-color: #495057 }.page-link:focus {box-shadow: 0 0 0 .2rem rgba(0,123,255,.25) }.page-item.active .page-link {color: #000;background-color: #3395ff;border-color: #3395ff }.page-item.disabled .page-link {color: #ced4da;background-color: #000;border-color: #495057 }}@media (prefers-color-scheme:dark) {.badge-primary {color: #e9ecef;background-color: #3395ff }a.badge-primary:focus, a.badge-primary:hover {color: #e9ecef;background-color: #007bff }a.badge-primary.focus, a.badge-primary:focus {box-shadow: 0 0 0 .2rem rgba(51,149,255,.5) }.badge-secondary {color: #e9ecef;background-color: #6c757d }a.badge-secondary:focus, a.badge-secondary:hover {color: #e9ecef;background-color: #545b62 }a.badge-secondary.focus, a.badge-secondary:focus {box-shadow: 0 0 0 .2rem rgba(108,117,125,.5) }.badge-success {color: #e9ecef;background-color: #28a745 }a.badge-success:focus, a.badge-success:hover {color: #e9ecef;background-color: #1e7e34 }a.badge-success.focus, a.badge-success:focus {box-shadow: 0 0 0 .2rem rgba(40,167,69,.5) }.badge-info {color: #e9ecef;background-color: #17a2b8 }a.badge-info:focus, a.badge-info:hover {color: #e9ecef;background-color: #117a8b }a.badge-info.focus, a.badge-info:focus {box-shadow: 0 0 0 .2rem rgba(23,162,184,.5) }.badge-warning {color: #343a40;background-color: #fd7e14 }a.badge-warning:focus, a.badge-warning:hover {color: #343a40;background-color: #dc6502 }a.badge-warning.focus, a.badge-warning:focus {box-shadow: 0 0 0 .2rem rgba(253,126,20,.5) }.badge-danger {color: #e9ecef;background-color: #dc3545 }a.badge-danger:focus, a.badge-danger:hover {color: #e9ecef;background-color: #bd2130 }a.badge-danger.focus, a.badge-danger:focus {box-shadow: 0 0 0 .2rem rgba(220,53,69,.5) }.badge-light {color: #343a40;background-color: #dee2e6 }a.badge-light:focus, a.badge-light:hover {color: #343a40;background-color: #c1c9d0 }a.badge-light.focus, a.badge-light:focus {box-shadow: 0 0 0 .2rem rgba(222,226,230,.5) }.badge-dark {color: #e9ecef;background-color: #343a40 }a.badge-dark:focus, a.badge-dark:hover {color: #e9ecef;background-color: #1d2124 }a.badge-dark.focus, a.badge-dark:focus {box-shadow: 0 0 0 .2rem rgba(52,58,64,.5) }.jumbotron {background-color: #343a40 }}@media (prefers-color-scheme:dark) {.alert {border: 1px solid transparent }.alert-heading {color: inherit }.alert-dismissible .close {color: inherit }.alert-primary {color: #1b4e85;background-color: #d6eaff;border-color: #c6e1ff }.alert-primary .alert-link {color: #12355b }.alert-secondary {color: #383d41;background-color: #e2e3e5;border-color: #d6d8db }.alert-secondary .alert-link {color: #202326 }.alert-success {color: #155724;background-color: #d4edda;border-color: #c3e6cb }.alert-success .alert-link {color: #0b2e13 }.alert-info {color: #0c5460;background-color: #d1ecf1;border-color: #bee5eb }.alert-info .alert-link {color: #062c33 }.alert-warning {color: #84420a;background-color: #ffe5d0;border-color: #fedbbd }.alert-warning .alert-link {color: #552a06 }.alert-danger {color: #721c24;background-color: #f8d7da;border-color: #f5c6cb }.alert-danger .alert-link {color: #491217 }.alert-light, .alert-themed-inverted {color: #737678;background-color: #f8f9fa;border-color: #f6f7f8 }.alert-light .alert-link, .alert-themed-inverted .alert-link {color: #5a5c5e }.alert-dark, .alert-themed {color: #1b1e21;background-color: #d6d8d9;border-color: #c6c8ca }.alert-dark .alert-link, .alert-themed .alert-link {color: #040505 }@-webkit-keyframes progress-bar-stripes {from {background-position: 1rem 0 }to {background-position: 0 0 }}@keyframes progress-bar-stripes {from {background-position: 1rem 0 }to {background-position: 0 0 }}.progress {background-color: #e9ecef }.progress-bar {color: #fff;background-color: #007bff }}@media (prefers-color-scheme:dark) {.progress-bar-striped {background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent) }}@media (prefers-color-scheme:dark) {.list-group-item-action {color: #dee2e6 }.list-group-item-action:focus, .list-group-item-action:hover {color: #dee2e6;background-color: #212529 }.list-group-item-action:active {color: #d3d3d3;background-color: #343a40 }.list-group-item {background-color: rgba(25,29,33,.05);border: 1px solid rgba(255,255,255,.125) }.list-group-item.disabled, .list-group-item:disabled {color: #ced4da;background-color: rgba(25,29,33,.05) }.list-group-item.active {color: #000;background-color: #3395ff;border-color: #3395ff }}@media (prefers-color-scheme:dark) {.list-group-item-primary {color: #1b4e85;background-color: #c6e1ff }.list-group-item-primary.list-group-item-action:focus, .list-group-item-primary.list-group-item-action:hover {color: #1b4e85;background-color: #add4ff }.list-group-item-primary.list-group-item-action.active {color: #fff;background-color: #1b4e85;border-color: #1b4e85 }.list-group-item-secondary {color: #383d41;background-color: #d6d8db }.list-group-item-secondary.list-group-item-action:focus, .list-group-item-secondary.list-group-item-action:hover {color: #383d41;background-color: #c8cbcf }.list-group-item-secondary.list-group-item-action.active {color: #fff;background-color: #383d41;border-color: #383d41 }.list-group-item-success {color: #155724;background-color: #c3e6cb }.list-group-item-success.list-group-item-action:focus, .list-group-item-success.list-group-item-action:hover {color: #155724;background-color: #b1dfbb }.list-group-item-success.list-group-item-action.active {color: #fff;background-color: #155724;border-color: #155724 }.list-group-item-info {color: #0c5460;background-color: #bee5eb }.list-group-item-info.list-group-item-action:focus, .list-group-item-info.list-group-item-action:hover {color: #0c5460;background-color: #abdde5 }.list-group-item-info.list-group-item-action.active {color: #fff;background-color: #0c5460;border-color: #0c5460 }.list-group-item-warning {color: #84420a;background-color: #fedbbd }.list-group-item-warning.list-group-item-action:focus, .list-group-item-warning.list-group-item-action:hover {color: #84420a;background-color: #fecda4 }.list-group-item-warning.list-group-item-action.active {color: #fff;background-color: #84420a;border-color: #84420a }.list-group-item-danger {color: #721c24;background-color: #f5c6cb }.list-group-item-danger.list-group-item-action:focus, .list-group-item-danger.list-group-item-action:hover {color: #721c24;background-color: #f1b0b7 }.list-group-item-danger.list-group-item-action.active {color: #fff;background-color: #721c24;border-color: #721c24 }.list-group-item-light {color: #737678;background-color: #f6f7f8 }.list-group-item-light.list-group-item-action:focus, .list-group-item-light.list-group-item-action:hover {color: #737678;background-color: #e8eaed }.list-group-item-light.list-group-item-action.active {color: #fff;background-color: #737678;border-color: #737678 }.list-group-item-dark {color: #1b1e21;background-color: #c6c8ca }.list-group-item-dark.list-group-item-action:focus, .list-group-item-dark.list-group-item-action:hover {color: #1b1e21;background-color: #b9bbbe }.list-group-item-dark.list-group-item-action.active {color: #fff;background-color: #1b1e21;border-color: #1b1e21 }.close {color: #fff;text-shadow: 0 1px 0 #000 }.close:hover {color: #fff }button.close {background-color: transparent;border: 0 }.toast {background-color: rgba(0,0,0,.85);border: 1px solid rgba(255,255,255,.1);box-shadow: 0 .25rem .75rem rgba(255,255,255,.1) }.toast-header {color: #ced4da;background-color: rgba(0,0,0,.85);border-bottom: 1px solid rgba(255,255,255,.05) }}@media (prefers-color-scheme:dark) {.modal-content {background-color: #191d21;border: 1px solid rgba(255,255,255,.2) }.modal-backdrop {background-color: #000 }.modal-header {border-bottom: 1px solid #343a40 }.modal-footer {border-top: 1px solid #343a40 }}@media (prefers-color-scheme:dark) {.tooltip {text-shadow: none }.tooltip .arrow::before {border-color: transparent }.tooltip-inner {color: #fff;background-color: #000 }.popover {text-shadow: none;background-color: #fff;border: 1px solid rgba(0,0,0,.2) }.popover .arrow::after, .popover .arrow::before {border-color: transparent }.bs-popover-auto[x-placement^=bottom] .popover-header::before, .bs-popover-bottom .popover-header::before {border-bottom: 1px solid #f7f7f7 }.popover-header {background-color: #f7f7f7;border-bottom: 1px solid #ebebeb }.popover-body {color: #212529 }}@media (prefers-color-scheme:dark) {.carousel-control-next, .carousel-control-prev {color: #fff;background: 0 0;border: 0 }}@media (prefers-color-scheme:dark) {.carousel-control-next:focus, .carousel-control-next:hover, .carousel-control-prev:focus, .carousel-control-prev:hover {color: #fff }.carousel-control-next-icon, .carousel-control-prev-icon {background: 50%/100% 100% no-repeat }.carousel-control-prev-icon {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e") }.carousel-control-next-icon {background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e") }.carousel-indicators li {background-color: #fff;border-top: 10px solid transparent;border-bottom: 10px solid transparent }}@media (prefers-color-scheme:dark) {.carousel-caption {color: #fff }@-webkit-keyframes spinner-border {to {transform: rotate(360deg) }}@keyframes spinner-border {to {transform: rotate(360deg) }}.spinner-border {border: .25em solid currentcolor }@-webkit-keyframes spinner-grow {0% {transform: scale(0) }50% {opacity: 1;transform: none }}@keyframes spinner-grow {0% {transform: scale(0) }50% {opacity: 1;transform: none }}.spinner-grow {background-color: currentcolor }}@media (prefers-color-scheme:dark) {.bg-primary {background-color: #3395ff!important }a.bg-primary:focus, a.bg-primary:hover, button.bg-primary:focus, button.bg-primary:hover {background-color: #007bff!important }.bg-secondary {background-color: #6c757d!important }a.bg-secondary:focus, a.bg-secondary:hover, button.bg-secondary:focus, button.bg-secondary:hover {background-color: #545b62!important }.bg-success {background-color: #28a745!important }a.bg-success:focus, a.bg-success:hover, button.bg-success:focus, button.bg-success:hover {background-color: #1e7e34!important }.bg-info {background-color: #17a2b8!important }a.bg-info:focus, a.bg-info:hover, button.bg-info:focus, button.bg-info:hover {background-color: #117a8b!important }.bg-warning {background-color: #fd7e14!important }a.bg-warning:focus, a.bg-warning:hover, button.bg-warning:focus, button.bg-warning:hover {background-color: #dc6502!important }.bg-danger {background-color: #dc3545!important }a.bg-danger:focus, a.bg-danger:hover, button.bg-danger:focus, button.bg-danger:hover {background-color: #bd2130!important }.bg-light, .bg-themed-inverted {background-color: #dee2e6!important }a.bg-light:focus, a.bg-light:hover, a.bg-themed-inverted:focus, a.bg-themed-inverted:hover, button.bg-light:focus, button.bg-light:hover, button.bg-themed-inverted:focus, button.bg-themed-inverted:hover {background-color: #c1c9d0!important }.bg-dark, .bg-themed, .navbar-themed {background-color: #343a40!important }a.bg-dark:focus, a.bg-dark:hover, a.bg-themed:focus, a.bg-themed:hover, a.navbar-themed:focus, a.navbar-themed:hover, button.bg-dark:focus, button.bg-dark:hover, button.bg-themed:focus, button.bg-themed:hover, button.navbar-themed:focus, button.navbar-themed:hover {background-color: #1d2124!important }.bg-white {background-color: #fff!important }.bg-transparent {background-color: transparent!important }.border {border: 1px solid #343a40!important }.border-top {border-top: 1px solid #343a40!important }.border-right {border-right: 1px solid #343a40!important }.border-bottom {border-bottom: 1px solid #343a40!important }.border-left {border-left: 1px solid #343a40!important }.border-0 {border: 0!important }.border-top-0 {border-top: 0!important }.border-right-0 {border-right: 0!important }.border-bottom-0 {border-bottom: 0!important }.border-left-0 {border-left: 0!important }.border-primary {border-color: #3395ff!important }.border-secondary {border-color: #6c757d!important }.border-success {border-color: #28a745!important }.border-info {border-color: #17a2b8!important }.border-warning {border-color: #fd7e14!important }.border-danger {border-color: #dc3545!important }.border-light {border-color: #dee2e6!important }.border-dark {border-color: #343a40!important }.border-white {border-color: #fff!important }}@media (prefers-color-scheme:dark) {.embed-responsive .embed-responsive-item, .embed-responsive embed, .embed-responsive iframe, .embed-responsive object, .embed-responsive video {border: 0 }}@media (prefers-color-scheme:dark) {@supports ((position:-webkit-sticky) or (position:sticky)) {.sticky-top {position: -webkit-sticky;position: sticky;top: 0;z-index: 1020 }}.sr-only {border: 0 }.shadow-sm {box-shadow: 0 .125rem .25rem rgba(0,0,0,.075)!important }.shadow {box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important }.shadow-lg {box-shadow: 0 1rem 3rem rgba(0,0,0,.175)!important }.shadow-none {box-shadow: none!important }}@media (prefers-color-scheme:dark) {.stretched-link::after {background-color: rgba(0,0,0,0) }}@media (prefers-color-scheme:dark) {.text-white {color: #fff!important }.text-primary {color: #3395ff!important }a.text-primary:focus, a.text-primary:hover {color: #006fe6!important }.text-secondary {color: #6c757d!important }a.text-secondary:focus, a.text-secondary:hover {color: #494f54!important }.text-success {color: #28a745!important }a.text-success:focus, a.text-success:hover {color: #19692c!important }.text-info {color: #17a2b8!important }a.text-info:focus, a.text-info:hover {color: #0f6674!important }.text-warning {color: #fd7e14!important }a.text-warning:focus, a.text-warning:hover {color: #c35a02!important }.text-danger {color: #dc3545!important }a.text-danger:focus, a.text-danger:hover {color: #a71d2a!important }.text-light, .text-themed {color: #dee2e6!important }a.text-light:focus, a.text-light:hover, a.text-themed:focus, a.text-themed:hover {color: #b2bcc5!important }.text-dark, .text-themed-inverted {color: #343a40!important }a.text-dark:focus, a.text-dark:hover, a.text-themed-inverted:focus, a.text-themed-inverted:hover {color: #121416!important }.text-body {color: #d3d3d3!important }.text-muted {color: #6c757d!important }.text-black-50 {color: rgba(0,0,0,.5)!important }.text-white-50 {color: rgba(255,255,255,.5)!important }.text-hide {color: transparent;text-shadow: none;background-color: transparent;border: 0 }.text-reset {color: inherit!important }}
\ No newline at end of file
/*
Copyright (C) Federico Zivolo 2019
Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
*/(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'top',o='top'===t?'scrollTop':'scrollLeft',n=e.nodeName;if('BODY'===n||'HTML'===n){var i=e.ownerDocument.documentElement,r=e.ownerDocument.scrollingElement||i;return r[o]}return e[o]}function f(e,t){var o=2<arguments.length&&void 0!==arguments[2]&&arguments[2],n=l(t,'top'),i=l(t,'left'),r=o?-1:1;return e.top+=n*r,e.bottom+=n*r,e.left+=i*r,e.right+=i*r,e}function m(e,t){var o='x'===t?'Left':'Top',n='Left'==o?'Right':'Bottom';return parseFloat(e['border'+o+'Width'],10)+parseFloat(e['border'+n+'Width'],10)}function h(e,t,o,n){return ee(t['offset'+e],t['scroll'+e],o['client'+e],o['offset'+e],o['scroll'+e],r(10)?parseInt(o['offset'+e])+parseInt(n['margin'+('Height'===e?'Top':'Left')])+parseInt(n['margin'+('Height'===e?'Bottom':'Right')]):0)}function c(e){var t=e.body,o=e.documentElement,n=r(10)&&getComputedStyle(o);return{height:h('Height',t,o,n),width:h('Width',t,o,n)}}function g(e){return le({},e,{right:e.left+e.width,bottom:e.top+e.height})}function u(e){var o={};try{if(r(10)){o=e.getBoundingClientRect();var n=l(e,'top'),i=l(e,'left');o.top+=n,o.left+=i,o.bottom+=n,o.right+=i}else o=e.getBoundingClientRect()}catch(t){}var p={left:o.left,top:o.top,width:o.right-o.left,height:o.bottom-o.top},s='HTML'===e.nodeName?c(e.ownerDocument):{},d=s.width||e.clientWidth||p.width,a=s.height||e.clientHeight||p.height,f=e.offsetWidth-d,h=e.offsetHeight-a;if(f||h){var u=t(e);f-=m(u,'x'),h-=m(u,'y'),p.width-=f,p.height-=h}return g(p)}function b(e,o){var i=2<arguments.length&&void 0!==arguments[2]&&arguments[2],p=r(10),s='HTML'===o.nodeName,d=u(e),a=u(o),l=n(e),m=t(o),h=parseFloat(m.borderTopWidth,10),c=parseFloat(m.borderLeftWidth,10);i&&s&&(a.top=ee(a.top,0),a.left=ee(a.left,0));var b=g({top:d.top-a.top-h,left:d.left-a.left-c,width:d.width,height:d.height});if(b.marginTop=0,b.marginLeft=0,!p&&s){var w=parseFloat(m.marginTop,10),y=parseFloat(m.marginLeft,10);b.top-=h-w,b.bottom-=h-w,b.left-=c-y,b.right-=c-y,b.marginTop=w,b.marginLeft=y}return(p&&!i?o.contains(l):o===l&&'BODY'!==l.nodeName)&&(b=f(b,o)),b}function w(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=e.ownerDocument.documentElement,n=b(e,o),i=ee(o.clientWidth,window.innerWidth||0),r=ee(o.clientHeight,window.innerHeight||0),p=t?0:l(o),s=t?0:l(o,'left'),d={top:p-n.top+n.marginTop,left:s-n.left+n.marginLeft,width:i,height:r};return g(d)}function y(e){var n=e.nodeName;if('BODY'===n||'HTML'===n)return!1;if('fixed'===t(e,'position'))return!0;var i=o(e);return!!i&&y(i)}function E(e){if(!e||!e.parentElement||r())return document.documentElement;for(var o=e.parentElement;o&&'none'===t(o,'transform');)o=o.parentElement;return o||document.documentElement}function v(e,t,r,p){var s=4<arguments.length&&void 0!==arguments[4]&&arguments[4],d={top:0,left:0},l=s?E(e):a(e,i(t));if('viewport'===p)d=w(l,s);else{var f;'scrollParent'===p?(f=n(o(t)),'BODY'===f.nodeName&&(f=e.ownerDocument.documentElement)):'window'===p?f=e.ownerDocument.documentElement:f=p;var m=b(f,l,s);if('HTML'===f.nodeName&&!y(l)){var h=c(e.ownerDocument),g=h.height,u=h.width;d.top+=m.top-m.marginTop,d.bottom=g+m.top,d.left+=m.left-m.marginLeft,d.right=u+m.left}else d=m}r=r||0;var v='number'==typeof r;return d.left+=v?r:r.left||0,d.top+=v?r:r.top||0,d.right-=v?r:r.right||0,d.bottom-=v?r:r.bottom||0,d}function x(e){var t=e.width,o=e.height;return t*o}function O(e,t,o,n,i){var r=5<arguments.length&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf('auto'))return e;var p=v(o,n,r,i),s={top:{width:p.width,height:t.top-p.top},right:{width:p.right-t.right,height:p.height},bottom:{width:p.width,height:p.bottom-t.bottom},left:{width:t.left-p.left,height:p.height}},d=Object.keys(s).map(function(e){return le({key:e},s[e],{area:x(s[e])})}).sort(function(e,t){return t.area-e.area}),a=d.filter(function(e){var t=e.width,n=e.height;return t>=o.clientWidth&&n>=o.clientHeight}),l=0<a.length?a[0].key:d[0].key,f=e.split('-')[1];return l+(f?'-'+f:'')}function L(e,t,o){var n=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,r=n?E(t):a(t,i(o));return b(o,r,n)}function S(e){var t=e.ownerDocument.defaultView,o=t.getComputedStyle(e),n=parseFloat(o.marginTop||0)+parseFloat(o.marginBottom||0),i=parseFloat(o.marginLeft||0)+parseFloat(o.marginRight||0),r={width:e.offsetWidth+i,height:e.offsetHeight+n};return r}function T(e){var t={left:'right',right:'left',bottom:'top',top:'bottom'};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function C(e,t,o){o=o.split('-')[0];var n=S(e),i={width:n.width,height:n.height},r=-1!==['right','left'].indexOf(o),p=r?'top':'left',s=r?'left':'top',d=r?'height':'width',a=r?'width':'height';return i[p]=t[p]+t[d]/2-n[d]/2,i[s]=o===s?t[s]-n[a]:t[T(s)],i}function D(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function N(e,t,o){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===o});var n=D(e,function(e){return e[t]===o});return e.indexOf(n)}function P(t,o,n){var i=void 0===n?t:t.slice(0,N(t,'name',n));return i.forEach(function(t){t['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');var n=t['function']||t.fn;t.enabled&&e(n)&&(o.offsets.popper=g(o.offsets.popper),o.offsets.reference=g(o.offsets.reference),o=n(o,t))}),o}function k(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=L(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=O(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=C(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?'fixed':'absolute',e=P(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function W(e,t){return e.some(function(e){var o=e.name,n=e.enabled;return n&&o===t})}function B(e){for(var t=[!1,'ms','Webkit','Moz','O'],o=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<t.length;n++){var i=t[n],r=i?''+i+o:e;if('undefined'!=typeof document.body.style[r])return r}return null}function H(){return this.state.isDestroyed=!0,W(this.modifiers,'applyStyle')&&(this.popper.removeAttribute('x-placement'),this.popper.style.position='',this.popper.style.top='',this.popper.style.left='',this.popper.style.right='',this.popper.style.bottom='',this.popper.style.willChange='',this.popper.style[B('transform')]=''),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function A(e){var t=e.ownerDocument;return t?t.defaultView:window}function M(e,t,o,i){var r='BODY'===e.nodeName,p=r?e.ownerDocument.defaultView:e;p.addEventListener(t,o,{passive:!0}),r||M(n(p.parentNode),t,o,i),i.push(p)}function F(e,t,o,i){o.updateBound=i,A(e).addEventListener('resize',o.updateBound,{passive:!0});var r=n(e);return M(r,'scroll',o.updateBound,o.scrollParents),o.scrollElement=r,o.eventsEnabled=!0,o}function I(){this.state.eventsEnabled||(this.state=F(this.reference,this.options,this.state,this.scheduleUpdate))}function R(e,t){return A(e).removeEventListener('resize',t.updateBound),t.scrollParents.forEach(function(e){e.removeEventListener('scroll',t.updateBound)}),t.updateBound=null,t.scrollParents=[],t.scrollElement=null,t.eventsEnabled=!1,t}function U(){this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=R(this.reference,this.state))}function Y(e){return''!==e&&!isNaN(parseFloat(e))&&isFinite(e)}function V(e,t){Object.keys(t).forEach(function(o){var n='';-1!==['width','height','top','right','bottom','left'].indexOf(o)&&Y(t[o])&&(n='px'),e.style[o]=t[o]+n})}function j(e,t){Object.keys(t).forEach(function(o){var n=t[o];!1===n?e.removeAttribute(o):e.setAttribute(o,t[o])})}function q(e,t){var o=e.offsets,n=o.popper,i=o.reference,r=$,p=function(e){return e},s=r(i.width),d=r(n.width),a=-1!==['left','right'].indexOf(e.placement),l=-1!==e.placement.indexOf('-'),f=t?a||l||s%2==d%2?r:Z:p,m=t?r:p;return{left:f(1==s%2&&1==d%2&&!l&&t?n.left-1:n.left),top:m(n.top),bottom:m(n.bottom),right:f(n.right)}}function K(e,t,o){var n=D(e,function(e){var o=e.name;return o===t}),i=!!n&&e.some(function(e){return e.name===o&&e.enabled&&e.order<n.order});if(!i){var r='`'+t+'`';console.warn('`'+o+'`'+' modifier is required by '+r+' modifier in order to work, be sure to include it before '+r+'!')}return i}function z(e){return'end'===e?'start':'start'===e?'end':e}function G(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=he.indexOf(e),n=he.slice(o+1).concat(he.slice(0,o));return t?n.reverse():n}function _(e,t,o,n){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+i[1],p=i[2];if(!r)return e;if(0===p.indexOf('%')){var s;switch(p){case'%p':s=o;break;case'%':case'%r':default:s=n;}var d=g(s);return d[t]/100*r}if('vh'===p||'vw'===p){var a;return a='vh'===p?ee(document.documentElement.clientHeight,window.innerHeight||0):ee(document.documentElement.clientWidth,window.innerWidth||0),a/100*r}return r}function X(e,t,o,n){var i=[0,0],r=-1!==['right','left'].indexOf(n),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(D(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,n){var i=(1===n?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return _(e,i,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,n){Y(o)&&(i[t]+=o*('-'===e[n-1]?-1:1))})}),i}function J(e,t){var o,n=t.offset,i=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=i.split('-')[0];return o=Y(+n)?[+n,0]:X(n,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}var Q=Math.min,Z=Math.floor,$=Math.round,ee=Math.max,te='undefined'!=typeof window&&'undefined'!=typeof document&&'undefined'!=typeof navigator,oe=function(){for(var e=['Edge','Trident','Firefox'],t=0;t<e.length;t+=1)if(te&&0<=navigator.userAgent.indexOf(e[t]))return 1;return 0}(),ne=te&&window.Promise,ie=ne?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},oe))}},re=te&&!!(window.MSInputMethodContext&&document.documentMode),pe=te&&/MSIE 10/.test(navigator.userAgent),se=function(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')},de=function(){function e(e,t){for(var o,n=0;n<t.length;n++)o=t[n],o.enumerable=o.enumerable||!1,o.configurable=!0,'value'in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}return function(t,o,n){return o&&e(t.prototype,o),n&&e(t,n),t}}(),ae=function(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},le=Object.assign||function(e){for(var t,o=1;o<arguments.length;o++)for(var n in t=arguments[o],t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},fe=te&&/Firefox/i.test(navigator.userAgent),me=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],he=me.slice(3),ce={FLIP:'flip',CLOCKWISE:'clockwise',COUNTERCLOCKWISE:'counterclockwise'},ge=function(){function t(o,n){var i=this,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};se(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=ie(this.update.bind(this)),this.options=le({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=o&&o.jquery?o[0]:o,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(le({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){i.options.modifiers[e]=le({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return le({name:e},i.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(t){t.enabled&&e(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)}),this.update();var p=this.options.eventsEnabled;p&&this.enableEventListeners(),this.state.eventsEnabled=p}return de(t,[{key:'update',value:function(){return k.call(this)}},{key:'destroy',value:function(){return H.call(this)}},{key:'enableEventListeners',value:function(){return I.call(this)}},{key:'disableEventListeners',value:function(){return U.call(this)}}]),t}();return ge.Utils=('undefined'==typeof window?global:window).PopperUtils,ge.placements=me,ge.Defaults={placement:'bottom',positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,o=t.split('-')[0],n=t.split('-')[1];if(n){var i=e.offsets,r=i.reference,p=i.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:ae({},d,r[d]),end:ae({},d,r[d]+r[a]-p[a])};e.offsets.popper=le({},p,l[n])}return e}},offset:{order:200,enabled:!0,fn:J,offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var o=t.boundariesElement||p(e.instance.popper);e.instance.reference===o&&(o=p(o));var n=B('transform'),i=e.instance.popper.style,r=i.top,s=i.left,d=i[n];i.top='',i.left='',i[n]='';var a=v(e.instance.popper,e.instance.reference,t.padding,o,e.positionFixed);i.top=r,i.left=s,i[n]=d,t.boundaries=a;var l=t.priority,f=e.offsets.popper,m={primary:function(e){var o=f[e];return f[e]<a[e]&&!t.escapeWithReference&&(o=ee(f[e],a[e])),ae({},e,o)},secondary:function(e){var o='right'===e?'left':'top',n=f[o];return f[e]>a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]<r(n[d])&&(e.offsets.popper[d]=r(n[d])-o[a]),o[d]>r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-u<s[m]&&(e.offsets.popper[m]-=s[m]-(d[c]-u)),d[m]+u>s[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)<f(l.right)||'top'===n&&f(a.bottom)>f(l.top)||'bottom'===n&&f(a.top)<f(l.bottom),h=f(a.left)<f(o.left),c=f(a.right)>f(o.right),g=f(a.top)<f(o.top),u=f(a.bottom)>f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottom<o.top||t.left>o.right||t.top>o.bottom||t.right<o.left){if(!0===e.hide)return e;e.hide=!0,e.attributes['x-out-of-boundaries']=''}else{if(!1===e.hide)return e;e.hide=!1,e.attributes['x-out-of-boundaries']=!1}return e}},computeStyle:{order:850,enabled:!0,fn:function(e,t){var o=t.x,n=t.y,i=e.offsets.popper,r=D(e.instance.modifiers,function(e){return'applyStyle'===e.name}).gpuAcceleration;void 0!==r&&console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');var s,d,a=void 0===r?t.gpuAcceleration:r,l=p(e.instance.popper),f=u(l),m={position:i.position},h=q(e,2>window.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
//# sourceMappingURL=popper.min.js.map