Mercurial > p > roundup > code
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 |
