Skip to content

Commit 9e34885

Browse files
authored
Merge pull request #179 from consideRatio/pr/inline-notes-orm.py
docs: add inline documentation to orm.py
2 parents f3c428a + e692f03 commit 9e34885

File tree

1 file changed

+65
-9
lines changed

1 file changed

+65
-9
lines changed

nativeauthenticator/orm.py

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,44 @@
1414

1515

1616
class UserInfo(Base):
17+
"""
18+
This class represents the information that NativeAuthenticator persists in
19+
JupyterHub's database.
20+
"""
21+
1722
__tablename__ = "users_info"
1823
id = Column(Integer, primary_key=True, autoincrement=True)
24+
25+
# username should be a JupyterHub username, normalized by the Authenticator
26+
# class normalize_username function.
1927
username = Column(String(128), nullable=False)
28+
29+
# password should be a bcrypt generated string that not only contains a
30+
# hashed password, but also the salt and cost that was used to hash the
31+
# password. Since bcrypt can extract the salt from this concatenation, this
32+
# can be used again during validation as salt.
2033
password = Column(LargeBinary, nullable=False)
34+
35+
# is_authorized is a boolean to indicate if the user has been authorized,
36+
# either by an admin, or by validating via an email for example.
2137
is_authorized = Column(Boolean, default=False)
38+
39+
# login_email_sent is boolean to indicate if a self approval email has been
40+
# sent out, as enabled by having a allow_self_approval_for configuration
41+
# set.
2242
login_email_sent = Column(Boolean, default=False)
43+
44+
# email is a un-encrypted string representing the email
2345
email = Column(String(128))
46+
47+
# has_2fa is a boolean that is being set to true if the user declares they
48+
# want to setup 2fa during sign-up.
2449
has_2fa = Column(Boolean, default=False)
50+
51+
# otp_secret (one-time password secret) is given to a user during setup of
52+
# 2fa. With a shared secret like this, both the user and nativeauthenticator
53+
# are enabled to generate the same one-time password's, which enables them
54+
# to be matched against each other.
2555
otp_secret = Column(String(16))
2656

2757
def __init__(self, **kwargs):
@@ -31,34 +61,60 @@ def __init__(self, **kwargs):
3161

3262
@classmethod
3363
def find(cls, db, username):
34-
"""Find a user info record by name.
35-
Returns None if not found"""
64+
"""
65+
Find a user info record by username.
66+
67+
Returns None if no user was found.
68+
"""
3669
return db.query(cls).filter(cls.username == username).first()
3770

3871
@classmethod
3972
def all_users(cls, db):
40-
"""Returns all available user records."""
73+
"""
74+
Returns all available user info records.
75+
"""
4176
return db.query(cls).all()
4277

43-
def is_valid_password(self, password):
44-
"""Checks if a password passed matches the
45-
password stored"""
46-
encoded_pw = bcrypt.hashpw(password.encode(), self.password)
47-
return encoded_pw == self.password
48-
4978
@classmethod
5079
def change_authorization(cls, db, username):
80+
"""
81+
Toggles the authorization status of a user info record.
82+
83+
Returns the user info record.
84+
"""
5185
user = db.query(cls).filter(cls.username == username).first()
5286
user.is_authorized = not user.is_authorized
5387
db.commit()
5488
return user
5589

90+
def is_valid_password(self, password):
91+
"""
92+
Checks if a provided password hashes to the hash we have stored in
93+
self.password.
94+
95+
Note that self.password has been set to the return value of calling
96+
bcrypt.hashpw(...) before, that returns a concatenation of the random
97+
salt used and the hashed salt+password combination. So, when we are
98+
passing self.password back to bcrypt.hashpw(...) as a salt, it is smart
99+
enough to extract and use only the salt that was originally used.
100+
"""
101+
return self.password == bcrypt.hashpw(password.encode(), self.password)
102+
56103
@validates("email")
57104
def validate_email(self, key, address):
105+
"""
106+
Validates any attempt to set the email field of a user info record.
107+
"""
58108
if not address:
59109
return
60110
assert re.match(r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", address)
61111
return address
62112

63113
def is_valid_token(self, token):
114+
"""
115+
Validates a time-based one-time password (TOTP) as generated by a user's
116+
2fa application against the TOTP generated locally by the onetimepass
117+
module. Assuming the user generated a TOTP with a common shared one-time
118+
password secret (otp_secret), these passwords should match.
119+
"""
64120
return onetimepass.valid_totp(token, self.otp_secret)

0 commit comments

Comments
 (0)