comparison roundup/cgi/client.py @ 7809:be6cb2e0d471

feat: add support for rotating jwt keys This allows jwt_secret to have multiple ',' separated secrets. The first/leftmost should be used to sign new JWTs. All of them are used (starting from left/newest) to try to verify a JWT. If the first secret is < 32 chars in length JWTs are disabled. If any of the other secrets are < 32 chars, the configuration code causes the software to exit. This prevents insecure (too short) secrets from being used. Updated doc examples and tests.
author John Rouillard <rouilj@ieee.org>
date Thu, 14 Mar 2024 19:04:19 -0400
parents cc4b11ab2f22
children 928c20d4344b
comparison
equal deleted inserted replaced
7808:6c5f8da9fca7 7809:be6cb2e0d471
1109 except ImportError: 1109 except ImportError:
1110 # no support for jwt, this is fine. 1110 # no support for jwt, this is fine.
1111 self.setHeader("WWW-Authenticate", "Basic") 1111 self.setHeader("WWW-Authenticate", "Basic")
1112 raise LoginError('Support for jwt disabled.') 1112 raise LoginError('Support for jwt disabled.')
1113 1113
1114 secret = self.db.config.WEB_JWT_SECRET 1114
1115 if len(secret) < 32: 1115 # If first ',' separated token is < 32, jwt is disabled.
1116 # If second or later tokens are < 32 chars, the config system
1117 # stops the tracker from starting so insecure tokens can not
1118 # be used.
1119 if len(self.db.config.WEB_JWT_SECRET[0]) < 32:
1116 # no support for jwt, this is fine. 1120 # no support for jwt, this is fine.
1117 self.setHeader("WWW-Authenticate", "Basic") 1121 self.setHeader("WWW-Authenticate", "Basic")
1118 raise LoginError('Support for jwt disabled by admin.') 1122 raise LoginError('Support for jwt disabled by admin.')
1119 1123
1120 try: # handle jwt exceptions 1124 last_error = "Unknown error validating bearer token."
1121 token = jwt.decode(challenge, secret, 1125
1122 algorithms=['HS256'], 1126 for secret in self.db.config.WEB_JWT_SECRET:
1123 audience=self.db.config.TRACKER_WEB, 1127 try: # handle jwt exceptions
1124 issuer=self.db.config.TRACKER_WEB) 1128 token = jwt.decode(challenge, secret,
1125 except jwt.exceptions.InvalidTokenError as err: 1129 algorithms=['HS256'],
1126 self.setHeader("WWW-Authenticate", "Basic, Bearer") 1130 audience=self.db.config.TRACKER_WEB,
1127 self.make_user_anonymous() 1131 issuer=self.db.config.TRACKER_WEB)
1128 raise LoginError(str(err)) 1132 return (token)
1129 1133
1130 return (token) 1134 except jwt.exceptions.InvalidSignatureError as err:
1135 # Try more signatures.
1136 # If all signatures generate InvalidSignatureError,
1137 # we exhaust the loop and last_error is used to
1138 # report the final (but not only) InvalidSignatureError
1139 last_error = str(err) # preserve for end of loop
1140 except jwt.exceptions.InvalidTokenError as err:
1141 self.setHeader("WWW-Authenticate", "Basic, Bearer")
1142 self.make_user_anonymous()
1143 raise LoginError(str(err))
1144
1145 # reach here only if no valid signature was found
1146 self.setHeader("WWW-Authenticate", "Basic, Bearer")
1147 self.make_user_anonymous()
1148 raise LoginError(last_error)
1149
1131 1150
1132 def determine_user(self, is_api=False): 1151 def determine_user(self, is_api=False):
1133 """Determine who the user is""" 1152 """Determine who the user is"""
1134 self.opendb('admin') 1153 self.opendb('admin')
1135 1154

Roundup Issue Tracker: http://roundup-tracker.org/