You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "shantanukhond (via GitHub)" <gi...@apache.org> on 2026/04/19 14:11:52 UTC

[PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

shantanukhond opened a new pull request, #39469:
URL: https://github.com/apache/superset/pull/39469

   ### SUMMARY
   
   This change improves the authentication backed by database. Adds password reset by admin, enforcing a configurable password policy, and recording password changes for auditing. 
   
   - **New endpoint `PUT /api/v1/me/`** is no longer used to change passwords when `AUTH_TYPE` is `AUTH_DB`. Instead, **`PUT /api/v1/me/password`** accepts `current_password` and `new_password`, verifies the current password, validates the new password against **`AUTH_DB_CONFIG`**, updates the credential, writes an **`auth_audit_log`** row, and refreshes the session safely after a successful change.
   - **`AUTH_DB_CONFIG`** defines minimum length, required character classes, and a blocklist of common weak passwords; validation is shared conceptually between backend enforcement and optional client-side generation helpers.
   - **Admin password reset** on the users list continues to use the existing security user update API, with UI support for generating a compliant password inline on the new-password field.
   - **Documentation** and **`UPDATING.md`** describe the new flow, policy knobs, and the breaking behavior for `PUT /api/v1/me/` under `AUTH_DB`.
   - **Tests**: unit coverage for policy and audit DAO; integration coverage for the password and profile APIs under `AUTH_DB`.
   
   Related: #37927  
   Migration standards context: [SIP-59 / #13351](https://github.com/apache/superset/issues/13351)
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   #### Before
   <img width="1470" height="913" alt="image" src="https://github.com/user-attachments/assets/6a3dcd78-fe08-41b6-a954-f93ef71c64ec" />
   
   #### After
   <img width="1466" height="912" alt="image" src="https://github.com/user-attachments/assets/c60dc42c-1c86-4dbe-ae0b-8daadf1c2d25" />
   <img width="1710" height="1107" alt="image" src="https://github.com/user-attachments/assets/fcbc44cb-cede-4768-b942-750e0ad8954e" />
   
   
   
   ### TESTING INSTRUCTIONS
   
   1. Configure **`AUTH_TYPE = AUTH_DB`** (and a DB auth user). Run **`superset db upgrade`** so the new migration is applied.
   2. **Self-service change**: open **User info** → **Reset my password**; submit with wrong current password → expect a clear error; submit with a weak new password → expect policy error; submit with valid passwords → expect success and remain logged in with the new password.
   3. **Profile without password**: **Edit user** from User info → **`PUT /api/v1/me/`** still updates name fields; confirm password is not accepted on that endpoint for `AUTH_DB`.
   4. **Admin reset**: as an admin, open **List users**, use **Reset password** for another user; save and confirm login works with the new password.
   5. **Audit**: after a successful self password change, confirm a row exists in **`auth_audit_log`** (or equivalent check your environment allows).
   6. **Migration**: run **`superset db downgrade`** one step and **`superset db upgrade`** again in a dev database to confirm the migration is reversible (adjust if your review process requires a specific DB engine).
   
   ### ADDITIONAL INFORMATION
   
   - [x] Has associated issue: #37927
   - [ ] Required feature flags:
   - [x] Changes UI
   - [x] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
     - [x] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [x] Introduces new feature or API
   - [x] Removes existing feature or API


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3206436683


##########
superset/views/users/schemas.py:
##########
@@ -15,16 +15,15 @@
 # specific language governing permissions and limitations
 # under the License.
 from flask_appbuilder.security.sqla.apis.user.schema import User
-from flask_appbuilder.security.sqla.apis.user.validator import (
-    PasswordComplexityValidator,
-)
-from marshmallow import fields, Schema
+from marshmallow import fields, Schema, validates_schema, ValidationError
 from marshmallow.fields import Boolean, Integer, String
 from marshmallow.validate import Length

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing Translation for Error Message</b></div>
   <div id="fix">
   
   The ValidationError message is user-facing (returned in API 400 responses) but not wrapped with a translation function, violating BITO rule [6516] for translatable user-facing text. This prevents localization support.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
    from marshmallow.validate import Length
    from flask_babel import lazy_gettext as _
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #eab5b7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset/views/users/api.py:
##########
@@ -163,6 +230,140 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put_password",
+        log_to_statsd=False,
+    )
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies ``AUTH_DB_CONFIG``
+            policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+        try:
+            body = self.current_user_password_put_schema.load(request.json or {})
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            validate_auth_db_password(body["new_password"])
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        user_db = db.session.get(User, g.user.id)
+        if user_db is None:
+            return self.response_404()
+
+        old_hash = user_db.password
+        if not check_password_hash(old_hash, body["current_password"]):
+            return self.response_400(message="Incorrect current password.")
+
+        try:
+            new_hash = generate_password_hash(
+                password=body["new_password"],
+                method=get_auth_db_password_hash_method(),
+                salt_length=app.config.get("FAB_PASSWORD_HASH_SALT_LENGTH", 16),
+            )
+        except ValidationError as error:
+            return self.response_400(message=error.messages)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Unreachable Exception Handler</b></div>
   <div id="fix">
   
   The except ValidationError block is unreachable because werkzeug.security.generate_password_hash does not raise ValidationError. This dead code should be removed to avoid confusion and maintain clean code.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #eab5b7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4403397476

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3502746759


##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import InvalidHash, VerifyMismatchError
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE = re.compile(r"^\$2[aby]\$\d{2}\$")
+_ARGON2_HASH_PREFIX = "$argon2"
+
+_argon2_hasher: PasswordHasher = PasswordHasher()
+
+
+def is_bcrypt_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the bcrypt wire format."""
+    return bool(_BCRYPT_HASH_RE.match(password_hash))
+
+
+def is_argon2_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the argon2 wire format."""
+    return password_hash.startswith(_ARGON2_HASH_PREFIX)
+
+
+def hash_auth_db_password(password: str, algorithm: str | None = None) -> str:
+    """
+    Hash a plaintext password for AUTH_DB storage.
+
+    Uses ``AUTH_DB_CONFIG["password_hash_algorithm"]`` when ``algorithm`` is omitted.
+    """
+    resolved = algorithm or get_auth_db_password_hash_algorithm()
+    if resolved == "argon2":
+        return _argon2_hasher.hash(password)
+    if resolved == "bcrypt":
+        encoded = password.encode("utf-8")
+        if len(encoded) > BCRYPT_MAX_PASSWORD_BYTES:
+            raise ValueError(
+                f"Password exceeds bcrypt's {BCRYPT_MAX_PASSWORD_BYTES}-byte limit."
+            )
+        return bcrypt.hashpw(encoded, bcrypt.gensalt()).decode("utf-8")
+    raise ValueError(f"Unsupported AUTH_DB hash algorithm: {resolved}")
+
+
+def verify_auth_db_password(password_hash: str, password: str) -> bool:
+    """
+    Verify a candidate password against a stored hash.
+
+    Supports bcrypt, argon2, and legacy Werkzeug hashes (for example scrypt, pbkdf2)
+    so existing users can log in until they rotate their password.
+    """
+    if not password_hash:
+        return False
+    if is_bcrypt_password_hash(password_hash):
+        try:
+            return bcrypt.checkpw(
+                password.encode("utf-8"),
+                password_hash.encode("utf-8"),
+            )
+        except (ValueError, TypeError):
+            return False

Review Comment:
   **Suggestion:** The bcrypt verification path does not enforce the 72-byte input limit, so passwords that differ only after byte 72 can still authenticate as equal. Add the same byte-length guard used during hashing and return False when the candidate exceeds the bcrypt limit to prevent truncated-password equivalence. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ⚠️ Different long passwords treated as same bcrypt credential.
   ⚠️ AUTH_DB login semantics inconsistent for overlong bcrypt passwords.
   ⚠️ Password change verification accepts ambiguous overlong bcrypt inputs.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset to use AUTH_DB so database password authentication goes through
   `_auth_user_db`, which calls `verify_auth_db_password(user.password, password)`
   (`superset/security/manager.py:4360-4375`).
   
   2. Ensure there is an AUTH_DB user record whose stored bcrypt password hash was generated
   from a plaintext longer than `BCRYPT_MAX_PASSWORD_BYTES` (72 bytes), for example by
   inserting a hash via SQL or from an earlier system that did not enforce the length limit
   (user is loaded at `superset/views/users/api.py:16-20` and
   `superset/security/manager.py:4360-4375`).
   
   3. Log in as that user through the standard AUTH_DB login flow so `_auth_user_db` invokes
   `verify_auth_db_password()` in `superset/utils/auth_db_password_hash.py:68-84` with the
   stored bcrypt hash and the candidate password provided by the user.
   
   4. Provide a candidate password whose first 72 UTF-8 bytes match the original but which
   differs after byte 72; in `verify_auth_db_password()` the bcrypt branch at
   `superset/utils/auth_db_password_hash.py:77-83` passes `password.encode("utf-8")` directly
   to `bcrypt.checkpw()` without enforcing the 72-byte limit, so bcrypt silently truncates
   the candidate to 72 bytes and returns `True`, allowing multiple distinct longer passwords
   with the same 72-byte prefix to authenticate as the same credential.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=5c3c1f0bdb5e468681f9b8199bd963a9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=5c3c1f0bdb5e468681f9b8199bd963a9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 77:84
   **Comment:**
   	*Security: The bcrypt verification path does not enforce the 72-byte input limit, so passwords that differ only after byte 72 can still authenticate as equal. Add the same byte-length guard used during hashing and return False when the candidate exceeds the bcrypt limit to prevent truncated-password equivalence.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=24c2da3fa2dfee7d5d40167e50d131459364cb38c89004897275234674019724&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=24c2da3fa2dfee7d5d40167e50d131459364cb38c89004897275234674019724&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import InvalidHash, VerifyMismatchError
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE = re.compile(r"^\$2[aby]\$\d{2}\$")
+_ARGON2_HASH_PREFIX = "$argon2"
+
+_argon2_hasher: PasswordHasher = PasswordHasher()
+
+
+def is_bcrypt_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the bcrypt wire format."""
+    return bool(_BCRYPT_HASH_RE.match(password_hash))
+
+
+def is_argon2_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the argon2 wire format."""
+    return password_hash.startswith(_ARGON2_HASH_PREFIX)
+
+
+def hash_auth_db_password(password: str, algorithm: str | None = None) -> str:
+    """
+    Hash a plaintext password for AUTH_DB storage.
+
+    Uses ``AUTH_DB_CONFIG["password_hash_algorithm"]`` when ``algorithm`` is omitted.
+    """
+    resolved = algorithm or get_auth_db_password_hash_algorithm()
+    if resolved == "argon2":
+        return _argon2_hasher.hash(password)
+    if resolved == "bcrypt":
+        encoded = password.encode("utf-8")
+        if len(encoded) > BCRYPT_MAX_PASSWORD_BYTES:
+            raise ValueError(
+                f"Password exceeds bcrypt's {BCRYPT_MAX_PASSWORD_BYTES}-byte limit."
+            )
+        return bcrypt.hashpw(encoded, bcrypt.gensalt()).decode("utf-8")
+    raise ValueError(f"Unsupported AUTH_DB hash algorithm: {resolved}")
+
+
+def verify_auth_db_password(password_hash: str, password: str) -> bool:
+    """
+    Verify a candidate password against a stored hash.
+
+    Supports bcrypt, argon2, and legacy Werkzeug hashes (for example scrypt, pbkdf2)
+    so existing users can log in until they rotate their password.
+    """
+    if not password_hash:
+        return False
+    if is_bcrypt_password_hash(password_hash):
+        try:
+            return bcrypt.checkpw(
+                password.encode("utf-8"),
+                password_hash.encode("utf-8"),
+            )
+        except (ValueError, TypeError):
+            return False
+    if is_argon2_password_hash(password_hash):
+        try:
+            _argon2_hasher.verify(password_hash, password)
+            return True
+        except (VerifyMismatchError, InvalidHash):
+            return False

Review Comment:
   **Suggestion:** The argon2 verify branch only handles mismatch/invalid-hash exceptions, but argon2 can also raise broader verification errors for malformed or corrupted hashes. Catch the broader verification exception class as well and return False so authentication failures do not bubble into 500 errors. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ❌ AUTH_DB login can 500 on corrupted argon2 hashes.
   ❌ Password change API 500s when argon2 hash invalid.
   ⚠️ CLI factory reset fails if admin argon2 hash bad.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Set `AUTH_DB_CONFIG["password_hash_algorithm"]` to `"argon2"` so
   `hash_auth_db_password()` at `superset/utils/auth_db_password_hash.py:49-57` stores argon2
   hashes for AUTH_DB users.
   
   2. Create or modify an AUTH_DB user such that the `password` column contains a malformed
   or corrupted argon2 hash (for example, truncating the stored hash via direct SQL), so
   `_argon2_hasher.verify()` is likely to raise a verification-related exception that is not
   `VerifyMismatchError` or `InvalidHash`.
   
   3. Attempt to log in with that user through the AUTH_DB login flow; `_auth_user_db` in
   `superset/security/manager.py:4360-4375` calls `verify_auth_db_password(user.password,
   password)` with the corrupted argon2 hash.
   
   4. Inside `verify_auth_db_password()` at `superset/utils/auth_db_password_hash.py:85-90`,
   the argon2 branch calls `_argon2_hasher.verify(password_hash, password)` and only catches
   `VerifyMismatchError` and `InvalidHash`, so a different argon2 verification exception
   propagates up the call stack, turning the authentication attempt (or the password change
   flow in `superset/views/users/api.py:11-24`) into an unhandled error that can surface as
   an HTTP 500 response instead of a clean authentication failure.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9c4cf584e19c45f6a89c1638a4166913&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9c4cf584e19c45f6a89c1638a4166913&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 85:90
   **Comment:**
   	*Api Mismatch: The argon2 verify branch only handles mismatch/invalid-hash exceptions, but argon2 can also raise broader verification errors for malformed or corrupted hashes. Catch the broader verification exception class as well and return False so authentication failures do not bubble into 500 errors.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ceae6c0dd39ee9764a7de184e17f93dc5522c2aabc11b661ced03cad97974766&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ceae6c0dd39ee9764a7de184e17f93dc5522c2aabc11b661ced03cad97974766&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "msyavuz (via GitHub)" <gi...@apache.org>.
msyavuz commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4972099439

   login_rate_limit, login_max_failures, and login_lockout_duration_minutes are shipped in AUTH_DB_DEFAULTS and documented, but the actual login path (auth_user_db) applies no rate limiting or lockout. The only consumer of get_auth_db_login_rate_limit_string() is the password-change endpoint (_rate_limit_me_password_change).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3602346381


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,265 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS: tuple[str, ...] = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS: frozenset[str] = frozenset({"bcrypt", "argon2"})
+
+# bcrypt silently truncates input beyond this UTF-8 byte limit.
+BCRYPT_MAX_PASSWORD_BYTES: int = 72
+
+_COMMON_PASSWORDS: frozenset[str] = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides: Any = app.config.get("AUTH_DB_CONFIG") or {}
+    if not isinstance(overrides, Mapping):
+        raise ValidationError(
+            {
+                "AUTH_DB_CONFIG": [
+                    "Invalid AUTH_DB_CONFIG. Expected a mapping of policy options."
+                ]
+            }
+        )
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    """
+    return str(
+        get_merged_auth_db_config().get(
+            "login_rate_limit", AUTH_DB_DEFAULTS["login_rate_limit"]
+        )
+    )
+
+
+def get_public_auth_db_password_policy(
+    cfg: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+    """
+    Return non-secret AUTH_DB password policy options for frontend validation UI.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    return {
+        key: merged_cfg.get(key, AUTH_DB_DEFAULTS[key])
+        for key in _PUBLIC_PASSWORD_POLICY_KEYS
+    }
+
+
+def get_auth_db_password_hash_algorithm(cfg: dict[str, Any] | None = None) -> str:
+    """
+    Return the password hash algorithm for AUTH_DB.
+
+    ``AUTH_DB_CONFIG["password_hash_algorithm"]`` accepts ``bcrypt`` (default)
+    or ``argon2``.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    raw_algorithm: Any = merged_cfg.get(
+        "password_hash_algorithm", AUTH_DB_DEFAULTS["password_hash_algorithm"]
+    )
+    algorithm = str(raw_algorithm).strip().lower()

Review Comment:
   **Suggestion:** Add an explicit type hint for this derived local value to satisfy the rule requiring type hints on relevant variables. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This new local variable is a derived string value that can be explicitly annotated, but the code omits a type hint. That is a real instance of the type-hint rule violation.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=cd608bb1ac794d9498b18f267452c72c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=cd608bb1ac794d9498b18f267452c72c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 155:155
   **Comment:**
   	*Custom Rule: Add an explicit type hint for this derived local value to satisfy the rule requiring type hints on relevant variables.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7e3bacc74df4044488d66eeb7888be154c7119477c6be11c4c660891f2b59530&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7e3bacc74df4044488d66eeb7888be154c7119477c6be11c4c660891f2b59530&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5023823373

   I think we're in a good state now. Thanks, @shantanukhond!
   
   The only thing still open at this point is the question that @rusackas  raised for @dpgaspar 
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3616477637


##########
superset/cli/reset.py:
##########
@@ -68,7 +69,11 @@ def factory_reset(
     # Validate the user
     password = click.prompt("Admin Password", hide_input=True)
     user = security_manager.find_user(username)
-    if not user or not check_password_hash(user.password, password):
+    if (
+        not user
+        or not user.is_active
+        or not verify_auth_db_password(user.password, password)

Review Comment:
   The CLI runs with shell access on the host, inside the operator trust boundary per SECURITY.md, so login-failure bookkeeping doesn't buy us anything there (that shell can read the metadata DB outright). Web logins keep the full security-manager flow. Resolving.



##########
superset-frontend/src/utils/generateAuthDbPassword.test.ts:
##########
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AUTH_DB_PASSWORD_MIN_LENGTH,
+  generateAuthDbPassword,
+  getAuthDbPasswordPolicyChecks,
+  getAuthDbPasswordPolicyError,
+  satisfiesDefaultAuthDbPasswordPolicy,
+} from './generateAuthDbPassword';
+
+test('generateAuthDbPassword returns policy-compliant passwords', () => {
+  for (let i = 0; i < 20; i += 1) {
+    const pwd = generateAuthDbPassword();
+    expect(Array.from(pwd).length).toBeGreaterThanOrEqual(
+      AUTH_DB_PASSWORD_MIN_LENGTH,
+    );
+    expect(satisfiesDefaultAuthDbPasswordPolicy(pwd)).toBe(true);
+  }
+});
+
+test('generateAuthDbPassword honors a custom minimum length policy', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_min_length: 20,
+  };
+  for (let i = 0; i < 10; i += 1) {
+    const pwd = generateAuthDbPassword(policy);
+    expect(Array.from(pwd).length).toBeGreaterThanOrEqual(20);
+    expect(getAuthDbPasswordPolicyError(pwd, policy)).toBeNull();
+  }
+});
+
+test('getAuthDbPasswordPolicyChecks counts Unicode code points for min length', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_min_length: 2,
+    password_require_uppercase: false,
+    password_require_lowercase: false,
+    password_require_digit: false,
+    password_require_special: false,
+    password_common_list_check: false,
+  };
+  expect(getAuthDbPasswordPolicyChecks('a😀', policy).minLength).toBe(true);
+  expect(getAuthDbPasswordPolicyChecks('😀', policy).minLength).toBe(false);
+});
+
+test('getAuthDbPasswordPolicyError respects disabled uppercase requirement', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_require_uppercase: false,
+  };
+  const password = 'abcdefghijklm1!';
+  expect(getAuthDbPasswordPolicyError(password, policy)).toBeNull();
+});
+
+test('getAuthDbPasswordPolicyError rejects bcrypt passwords over the byte limit', () => {
+  const password = `Aa1!${'x'.repeat(69)}`;
+  expect(getAuthDbPasswordPolicyError(password)).toMatch(/72 bytes/);
+});
+
+test('getAuthDbPasswordPolicyError skips bcrypt byte limit for argon2', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_hash_algorithm: 'argon2' as const,
+    password_require_uppercase: false,
+    password_require_lowercase: false,
+    password_require_digit: false,
+    password_require_special: false,
+    password_common_list_check: false,
+    password_min_length: 1,

Review Comment:
   Looks like this landed as the shared `POLICY_WITH_ONLY_MIN_LENGTH` fixture, resolving.



##########
superset/security/manager.py:
##########
@@ -1334,22 +1349,50 @@ def reset_password(self, userid: Union[int, str], password: str) -> None:
         bypassed. We distinguish the two by comparing the acting user
         (``g.user``) against the target ``userid``: they match for a
         self-service reset and differ for an admin reset.
+
+        On ``AUTH_DB``, the per-user session auth stamp is rotated so every
+        other session for the target account is invalidated — including when an
+        administrator resets a compromised password.
         """
+        # pylint: disable=import-outside-toplevel
+        from flask_appbuilder.const import AUTH_DB
+
         super().reset_password(userid, password)
 
+        try:
+            target_user_id = int(userid)
+        except (TypeError, ValueError):
+            target_user_id = None
+
         acting_user = getattr(g, "user", None)
         acting_user_id = getattr(acting_user, "id", None)
         # ``userid`` arrives as a string (the ``pk`` request arg) on the admin
         # path, so coerce both sides before comparing.
-        is_self_service = acting_user_id is not None and self._same_user(
-            acting_user_id, userid
+        is_self_service = (
+            acting_user_id is not None
+            and target_user_id is not None
+            and self._same_user(acting_user_id, userid)
         )
-        if is_self_service:
+
+        if (
+            current_app.config.get("AUTH_TYPE") == AUTH_DB
+            and target_user_id is not None
+        ):
+            from superset.utils.auth_session_stamp import (
+                bump_user_session_auth_stamp,
+                sync_session_auth_stamp_on_login,
+            )
+
+            bump_user_session_auth_stamp(target_user_id)
+            if is_self_service and acting_user is not None:
+                sync_session_auth_stamp_on_login(acting_user)
+
+        if is_self_service and target_user_id is not None:
             from superset.security.password_change import (
                 clear_password_must_change,
             )
 
-            clear_password_must_change(int(userid))
+            clear_password_must_change(target_user_id)

Review Comment:
   This landed... `UserPasswordChanged` now fires through the configured event logger for both the self-service and admin flows. Resolving.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3442937829


##########
superset/models/user_session_auth_stamp.py:
##########
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Per-user stamp used to invalidate browser sessions after password changes."""
+
+from __future__ import annotations
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from superset import security_manager
+
+
+class UserSessionAuthStamp(Model):
+    """
+    One row per ``ab_user`` holding a stamp copied into the Flask session at login.
+
+    Bumping ``stamp`` invalidates every session that still carries an older value.
+    """
+
+    __tablename__ = "user_session_auth_stamp"
+
+    user_id = Column(Integer, ForeignKey("ab_user.id", ondelete="CASCADE"), primary_key=True)
+    stamp = Column(String(36), nullable=False)
+
+    user = relationship(
+        security_manager.user_model,
+        foreign_keys=[user_id],
+    )
+
+    def __repr__(self) -> str:
+        return f"<UserSessionAuthStamp user_id={self.user_id}>"

Review Comment:
   **Suggestion:** Add an inline docstring to this newly added function so it complies with the requirement that new functions are documented. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The rule requires newly added Python functions to include docstrings. This new `__repr__` method has no docstring, so the suggestion correctly identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ccf58f7f2db54b5780fd82dd95042ca2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ccf58f7f2db54b5780fd82dd95042ca2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/user_session_auth_stamp.py
   **Line:** 45:46
   **Comment:**
   	*Custom Rule: Add an inline docstring to this newly added function so it complies with the requirement that new functions are documented.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=18d37c3930e2be13e23bb1150806c9aa6bbc79433e3c7c222c81f160eeda369c&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=18d37c3930e2be13e23bb1150806c9aa6bbc79433e3c7c222c81f160eeda369c&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4752212077

   > The session-before-commit fix looks good, thanks! Just a couple things still holding up the PR. @dpgaspar asked about reusing the existing `Log` table rather than a new `auth_audit_log` (plus its migration). Also, this is still the XXL bundle you and @EnxDev agreed to narrow down to Pillar 1, with the audit-log infra and admin reset riding along. Can we land just the `/me/password` change first and split the rest out? This also needs a rebase... happy to try tackling that part if it helps.
   
   Done! Changed from `auth_audit_log` to existing `log`. And I am just focusing on Pillar 1 Only.  I was just getting overwhelmed by all the comments by bots 😕. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3453903904


##########
superset/views/users/schemas.py:
##########
@@ -51,8 +51,30 @@ class CurrentUserPutSchema(Schema):
         metadata={"description": last_name_description},
         validate=[Length(1, 64)],
     )
-    password = fields.String(
-        required=False,
-        validate=[PasswordComplexityValidator()],
-        metadata={"description": password_description},
+
+
+class CurrentUserPasswordPutSchema(Schema):
+    """Payload for ``PUT /api/v1/me/password`` (AUTH_DB only)."""
+
+    current_password = fields.String(
+        required=True,
+        metadata={"description": current_password_description},
+        validate=[Length(min=1, max=256)],
     )
+    new_password = fields.String(
+        required=True,
+        metadata={"description": new_password_description},
+        validate=[Length(min=1, max=256)],
+    )
+    confirm_password = fields.String(
+        required=True,
+        metadata={"description": confirm_password_description},
+        validate=[Length(min=1, max=256)],
+    )
+
+    @validates_schema
+    def validate_new_password_confirmation(self, data: dict[str, str], **kwargs: object) -> None:
+        if data.get("new_password") != data.get("confirm_password"):
+            raise ValidationError(
+                {"confirm_password": [_("Confirmation must match new_password.")]}
+            )

Review Comment:
   **Suggestion:** Add a short docstring to the new schema validation method to document its purpose and validation behavior. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added Python method, and it has no docstring in the final file state.
   The custom rule explicitly requires new functions and classes to be documented inline, so the suggestion identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=22b199f7e3ee40689db56e6bbf84771e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=22b199f7e3ee40689db56e6bbf84771e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/schemas.py
   **Line:** 75:80
   **Comment:**
   	*Custom Rule: Add a short docstring to the new schema validation method to document its purpose and validation behavior.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8ed365a5a7b05640a624de8aa08fa8ee60de2a347333de6ca9f9603c41ceaf2d&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8ed365a5a7b05640a624de8aa08fa8ee60de2a347333de6ca9f9603c41ceaf2d&reaction=dislike'>👎</a>



##########
superset/views/users/api.py:
##########
@@ -14,47 +14,107 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
+import functools
+import logging
 from datetime import datetime
-from typing import Any, Dict
+from typing import Any, Callable, Dict
 
-from flask import current_app as app, g, redirect, request, Response
+from flask import current_app as app, g, redirect, request, Response, session
 from flask_appbuilder.api import expose, permission_name, safe
+from flask_appbuilder.const import AUTH_DB
 from flask_appbuilder.security.decorators import protect
 from flask_appbuilder.security.sqla.models import User
+from flask_login import login_user
 from marshmallow import ValidationError
+from sqlalchemy.exc import SQLAlchemyError
 from sqlalchemy.orm.exc import NoResultFound
-from werkzeug.security import generate_password_hash
 
 from superset import is_feature_enabled
 from superset.daos.user import UserDAO
-from superset.extensions import db, event_logger
+from superset.extensions import db, event_logger, security_manager
+from superset.utils.auth_db_password import (
+    get_auth_db_login_rate_limit_string,
+    get_public_auth_db_password_policy,
+    validate_auth_db_password,
+)
+from superset.utils.auth_db_password_hash import (
+    hash_auth_db_password,
+    verify_auth_db_password,
+)
+from superset.utils.auth_session_stamp import (
+    bump_user_session_auth_stamp,
+    clear_flask_login_remember_cookie,
+)
 from superset.utils.slack import get_user_avatar, SlackClientError
 from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics
-from superset.views.users.schemas import CurrentUserPutSchema, UserResponseSchema
+from superset.views.users.schemas import (
+    CurrentUserPasswordPutSchema,
+    CurrentUserPutSchema,
+    UserResponseSchema,
+)
 from superset.views.utils import bootstrap_user_data
 
+logger = logging.getLogger(__name__)
+
 user_response_schema = UserResponseSchema()
 
 
+def _get_client_ip() -> str | None:
+    """Return best-effort client IP from request context."""
+    if request.access_route:
+        return request.access_route[0]
+    return request.remote_addr
+
+
+def _me_password_rate_limit_key() -> str:
+    uid = getattr(getattr(g, "user", None), "id", None)
+    if uid is not None:
+        return f"me_password_uid:{uid}"
+    return _get_client_ip() or "unknown"
+
+
+def _rate_limit_me_password_change(
+    f: Callable[..., Response],
+) -> Callable[..., Response]:
+    """Apply AUTH_DB ``login_rate_limit`` when Flask-Limiter is enabled."""
+
+    @functools.wraps(f)
+    def wrapped(self: CurrentUserRestApi, *args: Any, **kwargs: Any) -> Response:
+        if not app.config.get("RATELIMIT_ENABLED", False):
+            return f(self, *args, **kwargs)
+        limiter = getattr(security_manager, "limiter", None)
+        if limiter is None:
+            return f(self, *args, **kwargs)
+        limited_view = limiter.limit(
+            get_auth_db_login_rate_limit_string(),
+            key_func=_me_password_rate_limit_key,
+            methods=["PUT"],
+        )(f)
+        return limited_view(self, *args, **kwargs)

Review Comment:
   **Suggestion:** Add a docstring to this new nested function so its behavior and purpose inside the decorator are explicitly documented. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a real violation of the docstring rule: the newly added nested function
   `wrapped` has no docstring. New Python functions should be documented inline.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=294c75922b8d48438f7328b3114da34a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=294c75922b8d48438f7328b3114da34a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 84:95
   **Comment:**
   	*Custom Rule: Add a docstring to this new nested function so its behavior and purpose inside the decorator are explicitly documented.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ae3d87b0cd51f98b671b4a91e066d8c4e07d59f66ac3bda6565856649fe4dbe4&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ae3d87b0cd51f98b671b4a91e066d8c4e07d59f66ac3bda6565856649fe4dbe4&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3602381830


##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -112,9 +112,9 @@ def downgrade() -> None:
     session = Session(bind=bind)
     do_downgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction

Review Comment:
   **Suggestion:** The same explicit `session.commit()` in `downgrade()` has the same transaction-boundary problem and can desynchronize downgrade behavior from Alembic’s transaction handling; this should also remain under Alembic-managed transaction control with flush-only ORM persistence. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Downgrade transaction boundaries inconsistent with Alembic env.py control.
   - ⚠️ Rollback of failed downgrade cannot fully revert permission changes.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run database downgrades via Superset (for example `superset db downgrade`), which uses
   Alembic’s env runner at `superset/migrations/env.py:87-142` to call
   `run_migrations_online()` and, for MySQL/SQLite, sets `transaction_per_migration=True` and
   `transactional_ddl=True` (line 118-119) while wrapping `context.run_migrations()` inside
   `context.begin_transaction()` (lines 131-133).
   
   2. During this migration run, Alembic executes revision `a7d3f1b9c2e4`’s `downgrade()`
   function defined at
   `superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:110-120`,
   which creates `session = Session(bind=bind)` (line 112) and calls `do_downgrade(session)`
   (line 113), currently a documented no-op (lines 86-96).
   
   3. After `do_downgrade()` returns, `downgrade()` explicitly calls `session.commit()` at
   line 115, committing the database transaction from inside the revision instead of leaving
   transaction control to Alembic’s `context.begin_transaction()` in `env.py`.
   
   4. If a subsequent downgrade step in the same Alembic-managed transaction fails and
   Alembic attempts to roll back, the manual commit has already finalized this revision’s
   effects, so downgrade behavior becomes desynchronized from Alembic’s transactional
   guarantees and rollback reliability now depends on this explicit commit rather than the
   configured per-migration transaction.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=cc55daf17da1446f90f5ce2530050160&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=cc55daf17da1446f90f5ce2530050160&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py
   **Line:** 115:115
   **Comment:**
   	*Logic Error: The same explicit `session.commit()` in `downgrade()` has the same transaction-boundary problem and can desynchronize downgrade behavior from Alembic’s transaction handling; this should also remain under Alembic-managed transaction control with flush-only ORM persistence.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=de7a172acd36d0a4ae31b4aeede99dd08fbac533eeae4b7325de7167c322cee1&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=de7a172acd36d0a4ae31b4aeede99dd08fbac533eeae4b7325de7167c322cee1&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3610463614


##########
superset/security/manager.py:
##########
@@ -4543,6 +4560,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )
+            logger.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+            if first_user:
+                self.noop_user_update(first_user)
+            return None
+        if verify_auth_db_password(user.password, password):
+            self.update_user_auth_stat(user, True)
+            return user

Review Comment:
   False positive: update_user_auth_stat(user, True) (line 4884) already triggers on_user_login(user) -- FAB's BaseSecurityManager.update_user_auth_stat calls self.on_user_login(user) on success internally, and SupersetSecurityManager overrides on_user_login to stamp login time, sync the session auth stamp, and log the UserLoggedIn audit event. Covered by test_auth_user_db_logs_single_user_logged_in_event. Resolving.



##########
superset/security/manager.py:
##########
@@ -4543,6 +4560,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )

Review Comment:
   Fixed: the fake-check path now calls verify_fake_auth_db_password(password), which verifies the actual candidate password against a precomputed hash in whichever algorithm is currently configured for AUTH_DB (bcrypt/argon2) instead of a fixed PBKDF2 hash. Resolving.



##########
superset/security/manager.py:
##########
@@ -4551,6 +4596,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )
+            logger.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)

Review Comment:
   Fixed, same as the other timing-balance thread on this function: the fake check now runs through verify_fake_auth_db_password(password), matching the configured AUTH_DB algorithm's cost instead of a fixed PBKDF2 hash. Resolving.



##########
superset/security/manager.py:
##########
@@ -4799,6 +4844,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )

Review Comment:
   Fixed, same as the other timing-balance threads on this function: the fake check now runs through verify_fake_auth_db_password(password), matching the configured AUTH_DB algorithm's cost. Resolving.



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,392 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register login and request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    from flask_login import user_logged_in
+
+    if not getattr(app, "superset_session_auth_stamp_login_connected", False):
+        app.superset_session_auth_stamp_login_connected = True
+
+        @user_logged_in.connect
+        def _sync_stamp_after_login(sender: Flask, user: Any, **extra: Any) -> None:  # noqa: ARG001
+            """Copy the DB stamp into the session after Flask-Login finalizes it."""
+            sync_session_auth_stamp_on_login(user)
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> str:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return new_stamp
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    return new_stamp
+
+
+def _resolve_stamp_check_user_id() -> int | None:
+    """Return the integer user id for the stamp check, or None to skip it."""
+    from flask import current_app
+    from flask_appbuilder.const import AUTH_DB
+    from sqlalchemy.orm.exc import DetachedInstanceError
+
+    if not has_request_context():
+        return None
+    # The stamp is only ever bumped on AUTH_DB password changes; skip the DB
+    # hit for other auth backends where the stamp never changes after login.
+    if current_app.config.get("AUTH_TYPE") != AUTH_DB:
+        return None
+    if not getattr(current_user, "is_authenticated", False):
+        return None
+    if getattr(current_user, "is_guest_user", False):
+        return None
+    try:
+        raw_id = current_user.get_id()
+    except DetachedInstanceError:
+        # The user object was detached from the session (e.g. after a rollback
+        # in a previous before_request hook). Fail open — Flask-Login will
+        # reload the user on the next request.
+        logger.warning(
+            "Skipping session auth stamp check: current_user is detached",
+            exc_info=True,
+        )
+        return None
+    if raw_id is None:
+        return None
+    try:
+        return int(raw_id)
+    except (TypeError, ValueError):
+        return None
+
+
+def _should_skip_stamp_db_lookup(
+    user_id: int, method: str, sess_stamp: str | None
+) -> bool:
+    """Return True when a recent DB check lets us skip another on read-only traffic."""
+    if method not in _SAFE_METHODS or sess_stamp is None:
+        return False
+
+    revalidation_seconds = _get_revalidation_seconds()
+    if revalidation_seconds <= 0:
+        return False
+
+    validated_at = session.get(SESSION_AUTH_STAMP_VALIDATED_AT_KEY)
+    if validated_at is None:
+        return False
+    try:
+        elapsed = time.time() - float(validated_at)
+    except (TypeError, ValueError):
+        return False
+    if elapsed >= revalidation_seconds:
+        return False
+
+    # Only skip when this session still carries the stamp we last validated and
+    # the shared cache agrees (so password rotation on another client is visible).
+    if session.get(SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY) != sess_stamp:
+        return False
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    return cached_stamp == sess_stamp
+
+
+def _mark_session_stamp_validated(db_stamp: str | None = None) -> None:
+    session[SESSION_AUTH_STAMP_VALIDATED_AT_KEY] = time.time()
+    if db_stamp is not None:
+        session[SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY] = db_stamp
+    session.modified = True
+
+
+def _load_db_stamp_for_user(
+    user_id: int, *, use_cache: bool = True
+) -> tuple[str | None, bool]:
+    """
+    Return ``(stamp, db_error)``.
+
+    ``db_error`` is True when the database lookup failed and the check should
+    fail open. ``stamp`` is None when no row exists.
+    """
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if use_cache:
+        cached_stamp = _get_cached_user_session_stamp(user_id)
+        if cached_stamp is not None:
+            return cached_stamp, False
+
+    try:
+        with db.session.begin_nested():
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            db_stamp = row.stamp if row is not None else None
+    except SQLAlchemyError:
+        logger.warning(
+            "Skipping session auth stamp check due to a database error",
+            exc_info=True,
+        )
+        return None, True
+
+    if db_stamp is not None:
+        _cache_user_session_stamp(user_id, db_stamp)
+    return db_stamp, False
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from flask import request as flask_request
+
+    user_id = _resolve_stamp_check_user_id()
+    if user_id is None:
+        logger.debug(
+            "Stamp validation skipped: no user_id for %s %s",
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)
+    if _should_skip_stamp_db_lookup(user_id, flask_request.method, sess_stamp):
+        logger.debug(
+            "Session auth stamp DB check skipped (recently validated) for user_id=%s "
+            "on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    db_stamp, db_error = _load_db_stamp_for_user(
+        user_id,
+        use_cache=(
+            flask_request.method in _SAFE_METHODS and _get_revalidation_seconds() > 0
+        ),
+    )
+    if db_error:
+        return
+
+    if db_stamp is None:
+        # No stamp row means the session predates stamp tracking. Allow the
+        # request without a check — sync_session_auth_stamp_on_login creates
+        # the row on every login, so this only occurs for sessions that
+        # pre-date the feature or whose row was removed independently.
+        # Committing inside a before_request hook would expire SQLAlchemy
+        # objects loaded by earlier hooks and can cause DetachedInstanceError
+        # in route handlers, so we never create the row here.
+        logger.debug(
+            "Session auth stamp check skipped: no DB stamp row for user_id=%s on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    # Compare the session cookie stamp against the authoritative DB value.
+    logger.debug(
+        "Validating session stamp for user_id=%s on %s %s: sess_stamp=%s, db_stamp=%s",
+        user_id,
+        flask_request.method,
+        flask_request.path,
+        sess_stamp,
+        db_stamp,
+    )
+    if sess_stamp is None or sess_stamp != db_stamp:
+        # Sessions authenticated before stamp tracking, or a login that did not
+        # persist the stamp into the signed cookie, carry no sess stamp. Adopt
+        # the current DB value so the request can proceed; password rotation
+        # still invalidates sessions that carry an outdated non-null stamp.
+        if sess_stamp is None and db_stamp is not None:
+            session[SESSION_AUTH_STAMP_SESSION_KEY] = db_stamp
+            _mark_session_stamp_validated(db_stamp)
+            _cache_user_session_stamp(user_id, db_stamp)
+            return

Review Comment:
   Fixed: stamp-less sessions are only auto-adopted for fresh interactive logins now (session.get("_fresh")); non-fresh sessions (e.g. restored from a remember-me cookie) fall through to _invalidate_stale_auth_session() instead of adopting the current DB stamp. Resolving.



##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -101,9 +101,9 @@ def upgrade() -> None:
     session = Session(bind=bind)
     do_upgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction

Review Comment:
   Fixed: upgrade() no longer calls session.commit()/rollback(); it flushes and lets Alembic's own transaction commit the permission changes together with the revision stamp. Resolving.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106961784


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,130 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_COMMON_PASSWORDS = frozenset(
+    {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",

Review Comment:
   **Suggestion:** The common-password check lowercases user input before lookup, but several entries in the common-password set are mixed case. Those entries will never match and weak passwords like case variants can bypass the blocklist. Normalize the blocklist values to lowercase so the comparison is consistent. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password change endpoint accepts weak common passwords like Passw0rd.
   - ⚠️ Admin user password resets may set weak blocked passwords.
   - ⚠️ AUTH_DB common-password policy weaker than configuration suggests.
   ```
   </details>
   
   ```suggestion
           "passw0rd",
           "password1",
           "password123",
           "welcome1",
           "qwerty123",
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run Superset with database authentication enabled (AUTH_TYPE = AUTH_DB in
   `superset/config.py:380-11`), using the default `AUTH_DB_CONFIG` which inherits
   `password_common_list_check=True` from `AUTH_DB_DEFAULTS` in
   `superset/utils/auth_db_password.py:27-40`.
   
   2. Log in as a normal user and call the current-user password change endpoint `PUT
   /api/v1/me/password` (method `update_my_password` decorated with `@expose("/password",
   methods=["PUT"])` in `superset/views/users/api.py:186-196`) with JSON body containing
   `"current_password": "<correct current password>"` and `"new_password": "Passw0rd"`.
   
   3. Inside `update_my_password`, the request body is deserialized by
   `self.current_user_password_put_schema.load(request.json or {})` and
   `validate_auth_db_password(body["new_password"])` is invoked at
   `superset/views/users/api.py:232-238` to enforce the AUTH_DB password policy.
   
   4. In `validate_auth_db_password` (`superset/utils/auth_db_password.py:93-127`), the
   common-password check lowercases the candidate password via `lowered =
   password.lower().strip()` at line 124 and checks `if lowered in _COMMON_PASSWORDS:` at
   line 126, while `_COMMON_PASSWORDS` defined at lines 42-83 contains `"Passw0rd"` (line 78)
   but not `"passw0rd"`, so `"Passw0rd"` is not detected as common and no `ValidationError`
   is raised.
   
   5. Because validation succeeds, `update_my_password` proceeds to update `g.user.password`
   and commit the transaction (hashing and audit logging at
   `superset/views/users/api.py:23-38` in the shown hunk), returning HTTP 200 and effectively
   allowing a password explicitly listed as common in `_COMMON_PASSWORDS`; the same bypass
   applies when an admin sets a user's password via the user edit flow, which calls
   `validate_auth_db_password(str(password))` in `SupersetSecurityManager.pre_update` at
   `superset/security/manager.py:210-217`.
   ```
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Futils%2Fauth_db_password.py%0A%2A%2ALine%3A%2A%2A%2078%3A82%0A%2A%2AComment%3A%2A%2A%0A%09%2ASecurity%3A%20The%20common-password%20check%20lowercases%20user%20input%20before%20lookup%2C%20but%20several%20entries%20in%20the%20common-password%20set%20are%20mixed%20case.%20Those%20entries%20will%20never%20match%20and%20weak%20passwords%20like%20case%20variants%20can%20bypass%20the%20blocklist.%20Normalize%20the%20blocklist%20values%20to%20lowercase%20so%20the%20comparison%20is%20consistent.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20durin
 g%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Futils%2Fauth_db_password.py%0A%2A%2ALine%3A%2A%2A%2078%3A82%0A%2A%2AComment%3A%2A%2A%0A%09%2ASecurity%3A%20The%20common-password%20check%20lowercases%20user%20input%20before%20lookup%2C%20but%20several%20entries%20in%20the%20common-password%20set%20are%20mixed%20case.%20Those%20entries%20will%20never%20match%20and%20weak%20passwords%20like%20case%20variants%20can%20bypass%20the%20blocklist.%20Normalize%20the%20blocklist%20values%20to%20lowercase%20so%20the%20comparison%20is%20consistent.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 78:82
   **Comment:**
   	*Security: The common-password check lowercases user input before lookup, but several entries in the common-password set are mixed case. Those entries will never match and weak passwords like case variants can bypass the blocklist. Normalize the blocklist values to lowercase so the comparison is consistent.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f7ea77087ae8100c67fc8e8504d7d7607cfc799585474b8929f8fbe84d3fee2e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f7ea77087ae8100c67fc8e8504d7d7607cfc799585474b8929f8fbe84d3fee2e&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4298274358

   Hey @shantanukhond! 👋 This review was performed by Claude (Amin's AI assistant) — I did an independent review and then cross-checked my findings against a second AI reviewer (Codex) for additional confidence.
   
   ## Key Findings (both reviewers agreed)
   
   ### Blockers
   
   1. **Session cleared before DB commit** (`api.py`, `update_my_password`): The session keys are popped and `login_user` is called *before* `db.session.commit()`. If the commit fails, the user's session is destroyed but the password wasn't actually changed — effectively locking them out. Recommend moving session clear + `login_user` to *after* a successful commit.
   
   2. **Audit log + password change not atomic**: `AuthAuditLogDAO.create()` calls `db.session.flush()`, but if the subsequent commit fails, you end up with partial state. Wrapping the whole operation in a try/except with `db.session.rollback()` on failure would make this safer.
   
   3. **Other active sessions not invalidated**: The current session is cleared, but other browser sessions or remembered sessions remain valid after a password change. If the password was changed due to a compromise, the attacker's session lives on.
   
   ### Medium Concerns
   
   4. **`pre_update` in `SupersetUserApi`** (`manager.py`): Reading from `request.get_json(silent=True)` directly bypasses FAB's schema layer, and raising `BadRequest` inside `pre_update` may not play well with FAB's `safe` decorator.
   
   5. **No rate limiting on `PUT /api/v1/me/password`**: The config defines `login_rate_limit` and `reset_rate_limit` but they aren't enforced on this endpoint, leaving it open to brute-force guessing of `current_password`.
   
   6. **`g.user` may be stale after commit**: After the commit, the SQLAlchemy object could be expired/detached. Should refresh the user from DB before passing to `login_user`.
   
   7. **Concurrent password change race**: Two concurrent requests with the same old password can both pass `check_password_hash` — last commit wins.
   
   ### Minor
   
   8. **`datetime.utcnow()` is deprecated** in Python 3.12+ — use `datetime.now(timezone.utc)` instead (`auth_audit_log.py`, `AuthAuditLog` model).
   
   9. **`metadata` physical column name** conflicts with SQLAlchemy's reserved `metadata` attribute. Consider naming the physical column `event_metadata` too.
   
   10. **Common password list duplicated** between `auth_db_password.py` and `generateAuthDbPassword.ts` — these will inevitably drift. Consider making the backend authoritative.
   
   11. **Modulo bias in `secureRandomInt`** (`generateAuthDbPassword.ts`): `buf[0] % maxExclusive` has slight bias for non-power-of-2 values. Negligible for password gen but worth noting.
   
   12. **Test cleanup fragility**: `test_put_my_password_success` manually resets the password without try/finally — if an assertion fails midway, subsequent tests may break.
   
   Overall this is a well-structured feature with good separation of concerns and solid test coverage. The session ordering issue (#1) is the most important fix. Nice work! 🙌


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4843726406

   @shantanukhond, the CI is still failing. Are you planning to take care of it, or would it be helpful if I gave you a hand?
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3428243796


##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,163 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { css, styled } from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import { Icons, Popover, Progress, Typography } from '@superset-ui/core/components';
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`
+    flex: 1;
+    cursor: help;
+
+    .ant-progress {
+      margin-bottom: 0;
+    }
+  `}
+`;
+
+const Checklist = styled.div`
+  ${({ theme }) => css`
+    min-width: ${theme.sizeUnit * 70}px;
+    display: flex;
+    flex-direction: column;
+    gap: ${theme.sizeUnit}px;
+  `}
+`;
+
+const ChecklistItem = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+    font-size: ${theme.fontSize}px;
+  `}
+`;
+
+const requirementText = {
+  minLength: (minLength: number) => t('At least %s characters', minLength),
+  uppercase: t('Contains an uppercase letter'),
+  lowercase: t('Contains a lowercase letter'),
+  digit: t('Contains a digit'),
+  special: t('Contains a special character'),
+  commonPassword: t('Is not a common password'),
+};
+
+function getStrengthState(percent: number) {
+  if (percent <= 33) {
+    return { color: '#cf1322', label: t('Very weak') };
+  }
+  if (percent <= 50) {
+    return { color: '#d46b08', label: t('Weak') };
+  }
+  if (percent <= 66) {
+    return { color: '#d4b106', label: t('Medium') };
+  }
+  if (percent <= 83) {
+    return { color: '#389e0d', label: t('Strong') };
+  }
+  return { color: '#08979c', label: t('Very strong') };

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Incorrect strength threshold boundaries</b></div>
   <div id="fix">
   
   Adjust the percent boundaries so that a rounded value of 67 (4/6 checks) maps to 'Medium'. For example, change the `if (percent <= 66)` check to `if (percent <= 67)` or adjust thresholds accordingly to align with Math.round() results.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #2fed89</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3432586643


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,259 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    crypto.getRandomValues(buf);

Review Comment:
   **Suggestion:** This directly uses the global `crypto` object without a capability check or fallback, which can throw at runtime in environments where `crypto.getRandomValues` is unavailable (for example some non-browser/test runtimes). Use `globalThis.crypto` with an explicit availability check (and fail with a clear error) before calling `getRandomValues`. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Clicking Generate password crashes when crypto API missing.
   - ⚠️ Unit tests using generateAuthDbPassword fail without crypto.
   - ⚠️ Error message currently unclear about missing crypto capability.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run code that renders either the reset-password fields in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:141-231` or
   `superset-frontend/src/features/users/UserListResetPasswordModal.tsx:130-207` in a
   JavaScript runtime where `globalThis.crypto` is undefined or lacks `getRandomValues` (for
   example a Node-based test environment without a Web Crypto polyfill).
   
   2. Click the "Generate password" suffix button wired via `GeneratePasswordInputSuffix` in
   those modals (`UserInfoModal.tsx:173-183` and `UserListResetPasswordModal.tsx:151-161`),
   which calls `generateAuthDbPassword(passwordPolicy)` from
   `superset-frontend/src/utils/generateAuthDbPassword.ts`.
   
   3. `generateAuthDbPassword` invokes `secureRandomInt` at
   `generateAuthDbPassword.ts:104-117`; inside the loop it allocates `const buf = new
   Uint32Array(1);` and unconditionally executes `crypto.getRandomValues(buf);` at line 113.
   
   4. Because `crypto` or `crypto.getRandomValues` is missing in that runtime, evaluating
   `crypto.getRandomValues(buf)` at `generateAuthDbPassword.ts:113` throws
   (ReferenceError/TypeError), crashing the password-generation call (including direct unit
   tests like `generateAuthDbPassword.test.ts:28-33`) instead of failing early with a clear
   "crypto API unavailable" error.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=5e2b2d710aa944f18be9c66dae4efb49&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=5e2b2d710aa944f18be9c66dae4efb49&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 113:113
   **Comment:**
   	*Api Mismatch: This directly uses the global `crypto` object without a capability check or fallback, which can throw at runtime in environments where `crypto.getRandomValues` is unavailable (for example some non-browser/test runtimes). Use `globalThis.crypto` with an explicit availability check (and fail with a clear error) before calling `getRandomValues`.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=40b27181ef2e5da495a02400aa90d1c0cb957952cc6ca14d73c753d616f64991&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=40b27181ef2e5da495a02400aa90d1c0cb957952cc6ca14d73c753d616f64991&reaction=dislike'>👎</a>



##########
superset-frontend/src/pages/UsersList/index.tsx:
##########
@@ -345,6 +352,13 @@ function UsersList({ user }: UsersListProps) {
                   icon: 'EditOutlined',
                   onClick: handleEdit,
                 },
+                {
+                  label: 'user-list-reset-password-action',
+                  tooltip: t('Reset password'),
+                  placement: 'bottom',
+                  icon: 'KeyOutlined',
+                  onClick: handleResetPassword,

Review Comment:
   **Suggestion:** The reset-password action is always exposed for admins without checking `AUTH_TYPE`, but this password-policy/reset flow is AUTH_DB-specific. In non-AUTH_DB deployments this surfaces a UI path that triggers failing/irrelevant password-policy calls and a misleading reset flow. Gate this action/modal behind an AUTH_DB check (same pattern used in `UserInfo`) so unsupported auth modes never show it. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Admin Reset password action shown for non-AUTH_DB auth.
   - ⚠️ Password policy endpoint returns 400 behind modal.
   - ⚠️ Admin may misinterpret reset as effective in OAuth.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset with a non-AUTH_DB auth type (for example AUTH_OAUTH) as in
   `tests/integration_tests/users/api_tests.py:test_get_my_password_policy_unavailable_when_not_auth_db`
   (lines 8–18), where `superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH`.
   
   2. Log in as an admin user and open the Users list page, which renders the `UsersList`
   component from `superset-frontend/src/pages/UsersList/index.tsx:67-83`.
   
   3. In the Users table "Actions" column, observe that when `isAdmin` is true the `actions`
   array at `index.tsx:347-362` always includes the `user-list-reset-password-action` defined
   at lines 356-360 (label `t('Reset password')`, icon `KeyOutlined`), with no check of
   `AUTH_TYPE`—in contrast to `superset-frontend/src/pages/UserInfo/index.tsx:123-150`, where
   the "Reset my password" button is gated on `authType === AuthType.AuthDB`.
   
   4. Click the "Reset password" action; this opens `UserListResetPasswordModal` via the
   conditional render at `index.tsx:554-580`, whose `useEffect` in
   `superset-frontend/src/features/users/UserListResetPasswordModal.tsx:60-75` calls `GET
   /api/v1/me/password/policy`. In non-AUTH_DB mode
   `CurrentUserRestApi.get_my_password_policy` (`superset/views/users/api.py:379-34`) returns
   HTTP 400 with message "Password policy is only available when AUTH_TYPE is AUTH_DB."
   (asserted in `tests/integration_tests/users/api_tests.py:8-18), so every use of this admin
   reset flow in such deployments hits an AUTH_DB-only policy endpoint and exposes a
   misleading AUTH_DB-specific reset UI.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f351aed975464e60876a67d7856bce84&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f351aed975464e60876a67d7856bce84&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/pages/UsersList/index.tsx
   **Line:** 356:360
   **Comment:**
   	*Api Mismatch: The reset-password action is always exposed for admins without checking `AUTH_TYPE`, but this password-policy/reset flow is AUTH_DB-specific. In non-AUTH_DB deployments this surfaces a UI path that triggers failing/irrelevant password-policy calls and a misleading reset flow. Gate this action/modal behind an AUTH_DB check (same pattern used in `UserInfo`) so unsupported auth modes never show it.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=545eaf5487587a13a4242debada16993af6c6ebb6830b02fb73b661141d77109&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=545eaf5487587a13a4242debada16993af6c6ebb6830b02fb73b661141d77109&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3425689348


##########
superset-frontend/src/features/users/UserListResetPasswordModal.tsx:
##########
@@ -0,0 +1,219 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+import { getClientErrorObject, SupersetClient } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
+import { useToasts } from 'src/components/MessageToasts/withToasts';
+import {
+  FormModal,
+  FormItem,
+  Input,
+  Icons,
+  type FormInstance,
+} from '@superset-ui/core/components';
+import { GeneratePasswordInputSuffix } from 'src/components/GeneratePasswordInputSuffix';
+import AuthDbPasswordPolicyIndicator from 'src/components/AuthDbPasswordPolicyIndicator';
+import {
+  AUTH_DB_PASSWORD_MIN_LENGTH,
+  generateAuthDbPassword,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+import { UserObject } from 'src/pages/UsersList/types';
+import { buildSecurityUserUpdatePayload } from './utils';
+
+export interface UserListResetPasswordModalProps {
+  show: boolean;
+  onHide: () => void;
+  onSave: () => void;
+  user: UserObject | null;
+}
+
+function UserListResetPasswordModal({
+  show,
+  onHide,
+  onSave,
+  user,
+}: UserListResetPasswordModalProps) {
+  const { addDangerToast, addSuccessToast } = useToasts();
+  const getPasswordPolicyError = (password: string): string | null => {
+    const checks = getAuthDbPasswordPolicyChecks(password);
+    if (!checks.minLength) {
+      return t(
+        'Password must be at least %s characters long.',
+        AUTH_DB_PASSWORD_MIN_LENGTH,
+      );
+    }
+    if (!checks.uppercase) {
+      return t('Password must contain at least one uppercase letter.');
+    }
+    if (!checks.lowercase) {
+      return t('Password must contain at least one lowercase letter.');
+    }
+    if (!checks.digit) {
+      return t('Password must contain at least one digit.');
+    }
+    if (!checks.special) {
+      return t(
+        'Password must contain at least one special character (not a letter, digit, or space).',
+      );
+    }
+    if (!checks.commonPassword) {
+      return t('Password is too common.');

Review Comment:
   **Suggestion:** The password validator is hard-wired to the default policy instead of the server policy, so admin reset can reject passwords that are valid for the current deployment (or allow ones the backend rejects). Fetch and apply the same `/api/v1/me/password/policy` policy object used elsewhere before validating. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Admin reset UI accepts or rejects passwords inconsistently.
   - ⚠️ Password policy inconsistent with self-service modal and backend.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure AUTH_DB with a non-default password policy via `AUTH_DB_CONFIG`, for example
   set `"password_require_uppercase": false` while keeping `AUTH_TYPE = AUTH_DB` (backend
   policy helpers at `superset/superset/utils/auth_db_password.py:27-41` and
   `validate_auth_db_password()` at lines 164-201 show these flags are honored server-side).
   
   2. As an admin, open the Users List page
   (`superset-frontend/src/pages/UsersList/index.tsx`), and click the "Reset password" action
   in the Actions column (handler `handleResetPassword` at lines 37-45 in the 300–360 block
   sets `currentUser` and opens `ModalType.RESET_PASSWORD`, which renders
   `<UserListResetPasswordModal>` at lines 68-81 in the 500–560 block).
   
   3. In `UserListResetPasswordModal`
   (`superset-frontend/src/features/users/UserListResetPasswordModal.tsx`), the password
   field uses the local `getPasswordPolicyError` validator at lines 54-79, which calls
   `getAuthDbPasswordPolicyChecks(password)` with no `policy` argument (line 55) and
   unconditionally checks `.uppercase`, `.lowercase`, `.digit`, `.special`, and
   `.commonPassword`, while also hard-coding the minimum length message to
   `AUTH_DB_PASSWORD_MIN_LENGTH` (lines 56-60).
   
   4. Enter a password that would be valid under the configured server policy but invalid
   under the default client policy, for example a long password with no uppercase when
   `password_require_uppercase` is disabled in `AUTH_DB_CONFIG`. The admin modal's validator
   still enforces the default "must contain at least one uppercase letter" rule and rejects
   the password client-side (lines 62-77), while the backend `validate_auth_db_password()`
   would accept it under the merged config—demonstrating the mismatch between admin reset UI
   and the actual server policy and the `/api/v1/me/password/policy`-backed flow used in
   `UserInfoModal` (`superset-frontend/src/features/userInfo/UserInfoModal.tsx` lines 54-83).
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d82b78f7d37a4e7499e76b0bf5f3b039&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d82b78f7d37a4e7499e76b0bf5f3b039&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/UserListResetPasswordModal.tsx
   **Line:** 55:77
   **Comment:**
   	*Api Mismatch: The password validator is hard-wired to the default policy instead of the server policy, so admin reset can reject passwords that are valid for the current deployment (or allow ones the backend rejects). Fetch and apply the same `/api/v1/me/password/policy` policy object used elsewhere before validating.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9c859ae236bd6863bfa91ff930bd946ffe8148a0ec40976ae51d09dfe01e4605&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9c859ae236bd6863bfa91ff930bd946ffe8148a0ec40976ae51d09dfe01e4605&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/users/utils.ts:
##########
@@ -19,8 +19,22 @@
 import { t } from '@apache-superset/core/translation';
 import { SupersetClient } from '@superset-ui/core';
 import { SelectOption } from 'src/components/ListView';
+import type { UserObject } from 'src/pages/UsersList/types';
 import { FormValues } from './types';
 
+/** Fields required for PUT `/api/v1/security/users/:id` when changing password only. */
+export function buildSecurityUserUpdatePayload(user: UserObject): FormValues {
+  return {
+    first_name: user.first_name,
+    last_name: user.last_name,
+    username: user.username,
+    email: user.email,
+    active: user.active,
+    roles: (user.roles ?? []).map(r => r.id),
+    groups: (user.groups ?? []).map(g => g.id),
+  };

Review Comment:
   **Suggestion:** This helper builds a full user update payload from the list row snapshot, so a password reset can unintentionally overwrite profile/authorization fields with stale values from the grid. Use a password-only endpoint/payload for reset operations, or re-fetch fresh user details right before PUT to avoid clobbering concurrent changes. [stale reference]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Password reset can silently revert user roles, groups, active flag.
   - ⚠️ Admin-facing Users List modal may clobber concurrent profile updates.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. An admin opens the Users List view (`superset-frontend/src/pages/UsersList/index.tsx`),
   which fetches user rows and renders them in the table. Each row's `original` object
   (including `first_name`, `last_name`, `email`, `active`, `roles`, and `groups`) is stored
   in the table state and passed through as `currentUser` when actions are triggered (see
   Action cell at lines 37-45 in the 300–360 block, and modal wiring at lines 68-81 in the
   500–560 block).
   
   2. Another admin (or the same admin in a different session) edits the user's profile via
   the "Edit user" action, which opens `UserListEditModal`
   (`superset-frontend/src/features/users/UserListModal.tsx`). In edit mode,
   `handleFormSubmit` calls `updateUser(user.id, values)` at lines 84-90, which performs
   `SupersetClient.put` to `/api/v1/security/users/:id` with the updated fields (`updateUser`
   implementation at `superset-frontend/src/features/users/utils.ts:49-53`), changing roles,
   groups, or active status server-side without refreshing the first admin's list view.
   
   3. Back in the original Users List tab with stale row data, the first admin clicks "Reset
   password" for that user. This opens `UserListResetPasswordModal`
   (`superset-frontend/src/features/users/UserListResetPasswordModal.tsx`), passing the stale
   `currentUser` object as the `user` prop (lines 68-81 in the 500–560 block). On submit,
   `handleFormSubmit` at lines 82-94 builds a payload using
   `buildSecurityUserUpdatePayload(user)` (line 92), then adds the new `password` field and
   sends a PUT to `/api/v1/security/users/${user.id}` (lines 95-99).
   
   4. `buildSecurityUserUpdatePayload` in
   `superset-frontend/src/features/users/utils.ts:26-35` constructs a full update object from
   the stale list snapshot: `first_name`, `last_name`, `username`, `email`, `active`, and
   mapped `roles`/`groups`. When `SupersetClient.put` sends this payload, it can overwrite
   more recent changes made in step 2 (for example, reactivating a user or restoring old
   roles/groups) because the stale data is treated as authoritative. This shows that using a
   list-row snapshot for a full user update during password reset can unintentionally clobber
   unrelated profile/authorization fields.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=226d43dbaaa24146a8e877daf387ed5c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=226d43dbaaa24146a8e877daf387ed5c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/utils.ts
   **Line:** 26:35
   **Comment:**
   	*Stale Reference: This helper builds a full user update payload from the list row snapshot, so a password reset can unintentionally overwrite profile/authorization fields with stale values from the grid. Use a password-only endpoint/payload for reset operations, or re-fetch fresh user details right before PUT to avoid clobbering concurrent changes.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c429f05c8780487f3b4185a8bf07def931e05b7e3a0916a8f12349315b6323b7&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c429f05c8780487f3b4185a8bf07def931e05b7e3a0916a8f12349315b6323b7&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/users/UserListResetPasswordModal.tsx:
##########
@@ -0,0 +1,219 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+import { getClientErrorObject, SupersetClient } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
+import { useToasts } from 'src/components/MessageToasts/withToasts';
+import {
+  FormModal,
+  FormItem,
+  Input,
+  Icons,
+  type FormInstance,
+} from '@superset-ui/core/components';
+import { GeneratePasswordInputSuffix } from 'src/components/GeneratePasswordInputSuffix';
+import AuthDbPasswordPolicyIndicator from 'src/components/AuthDbPasswordPolicyIndicator';
+import {
+  AUTH_DB_PASSWORD_MIN_LENGTH,
+  generateAuthDbPassword,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+import { UserObject } from 'src/pages/UsersList/types';
+import { buildSecurityUserUpdatePayload } from './utils';
+
+export interface UserListResetPasswordModalProps {
+  show: boolean;
+  onHide: () => void;
+  onSave: () => void;
+  user: UserObject | null;
+}
+
+function UserListResetPasswordModal({
+  show,
+  onHide,
+  onSave,
+  user,
+}: UserListResetPasswordModalProps) {
+  const { addDangerToast, addSuccessToast } = useToasts();
+  const getPasswordPolicyError = (password: string): string | null => {
+    const checks = getAuthDbPasswordPolicyChecks(password);
+    if (!checks.minLength) {
+      return t(
+        'Password must be at least %s characters long.',
+        AUTH_DB_PASSWORD_MIN_LENGTH,
+      );
+    }
+    if (!checks.uppercase) {
+      return t('Password must contain at least one uppercase letter.');
+    }
+    if (!checks.lowercase) {
+      return t('Password must contain at least one lowercase letter.');
+    }
+    if (!checks.digit) {
+      return t('Password must contain at least one digit.');
+    }
+    if (!checks.special) {
+      return t(
+        'Password must contain at least one special character (not a letter, digit, or space).',
+      );
+    }
+    if (!checks.commonPassword) {
+      return t('Password is too common.');
+    }
+    return null;
+  };
+
+  const handleFormSubmit = async (values: {
+    password: string;
+    confirmPassword: string;
+  }) => {
+    if (!user) {
+      return;
+    }
+    const { confirmPassword, ...rest } = values;
+    void confirmPassword;
+    const payload = {
+      ...buildSecurityUserUpdatePayload(user),
+      password: rest.password,
+    };
+    try {
+      await SupersetClient.put({
+        endpoint: `/api/v1/security/users/${user.id}`,
+        jsonPayload: payload,
+      });
+      addSuccessToast(
+        t('Password for %(username)s was updated successfully', {
+          username: user.username,
+        }),
+      );
+    } catch (error) {
+      const clientError = await getClientErrorObject(error);
+      const raw = clientError.message;
+      const text =
+        typeof raw === 'string'
+          ? raw
+          : raw && typeof raw === 'object'
+            ? (Object.values(raw).flat() as string[]).join(' ')
+            : t('Something went wrong while updating the password');
+      addDangerToast(text);
+      throw error;
+    }
+  };
+
+  return (
+    <FormModal
+      show={show}
+      onHide={onHide}
+      name="user-list-reset-password"
+      title={
+        <ModalTitleWithIcon
+          title={
+            user
+              ? `${t('Reset password')} — ${user.username}`
+              : t('Reset password')
+          }
+          icon={<Icons.KeyOutlined />}
+        />
+      }
+      onSave={onSave}
+      formSubmitHandler={handleFormSubmit}
+      requiredFields={['password', 'confirmPassword']}
+      initialValues={{}}
+    >
+      {(form: FormInstance) => (
+        <>
+          <FormItem
+            name="password"
+            label={t('New password')}
+            rules={[
+              { required: true, message: t('Password is required') },
+              {
+                validator(_, value) {
+                  const password = String(value ?? '');
+                  if (!password) {
+                    return Promise.resolve();
+                  }
+                  const errorMessage = getPasswordPolicyError(password);
+                  return errorMessage
+                    ? Promise.reject(new Error(errorMessage))
+                    : Promise.resolve();
+                },
+              },
+            ]}
+          >
+            <Input.Password
+              name="password"
+              autoComplete="new-password"
+              placeholder={t("Enter the user's new password")}
+              suffix={
+                <GeneratePasswordInputSuffix
+                  onGenerate={() => {
+                    const pwd = generateAuthDbPassword();
+                    form.setFieldsValue({ password: pwd, confirmPassword: pwd });

Review Comment:
   **Suggestion:** The inline password generator always uses default policy assumptions, so in environments with stricter `AUTH_DB_CONFIG` (for example longer minimum length) the generated password can fail on submit. Generate against the fetched policy (or validate/regenerate until it satisfies that policy) before setting form values. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Generated passwords often fail client validation under custom policy.
   - ⚠️ Users must tweak random password manually, harming UX.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure a stricter AUTH_DB password policy on the server, for example set
   `"password_min_length": 20` in `AUTH_DB_CONFIG` (merged by `get_merged_auth_db_config()`
   in `superset/superset/utils/auth_db_password.py:109-112`, and exposed via
   `get_public_auth_db_password_policy()` at lines 128-136 and `GET
   /api/v1/me/password/policy` in `superset/superset/views/users/api.py` around lines 20-53
   of the shown block).
   
   2. As a logged-in user changing their own password, open the profile modal
   (`UserInfoModal` in `superset-frontend/src/features/userInfo/UserInfoModal.tsx`). On
   initial show in reset-password mode, it fetches the effective policy from
   `/api/v1/me/password/policy` (useEffect at lines 58-73) and stores it in `passwordPolicy`,
   which is used for validation and strength indication (lines 75-101 and 223-226).
   
   3. Click the "Generate" icon on the "New password" field in `UserInfoModal`. This uses the
   same `GeneratePasswordInputSuffix` pattern as the admin modal, with `onGenerate={() => {
   const pwd = generateAuthDbPassword(); form.setFieldsValue({ new_password: pwd,
   confirm_password: pwd }); }}` at lines 203-208, where `generateAuthDbPassword()` is
   defined in `superset-frontend/src/utils/generateAuthDbPassword.ts:159-182` to generate a
   password that only satisfies the *default* policy (`AUTH_DB_PASSWORD_MIN_LENGTH = 12`
   etc., lines 24-41 and 122-136), not the fetched `passwordPolicy`.
   
   4. Because the generator does not take `passwordPolicy` into account, it can return a
   12–19 character password when `password_min_length` is 20; `getPasswordPolicyError()` in
   `UserInfoModal` (lines 75-81) then flags this generated password as too short, and the
   AntD form rejects submission until the user manually edits or regenerates—showing that the
   inline generator can produce passwords invalid under the current server policy. The same
   generator call appears in the admin reset modal at `UserListResetPasswordModal.tsx` lines
   165-169, so generated passwords there also only satisfy the default assumptions rather
   than any customized policy.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=91437e507b4e4671908cdc36be6d6838&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=91437e507b4e4671908cdc36be6d6838&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/UserListResetPasswordModal.tsx
   **Line:** 166:168
   **Comment:**
   	*Logic Error: The inline password generator always uses default policy assumptions, so in environments with stricter `AUTH_DB_CONFIG` (for example longer minimum length) the generated password can fail on submit. Generate against the fetched policy (or validate/regenerate until it satisfies that policy) before setting form values.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d60271a6d7d6319b16cfabaeb0650905275cb3c05052ded9e90fa55143cc6fe7&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d60271a6d7d6319b16cfabaeb0650905275cb3c05052ded9e90fa55143cc6fe7&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3423870415


##########
superset/utils/machine_auth.py:
##########
@@ -121,9 +121,12 @@ def get_cookies(self, user: User | None) -> dict[str, str]:
 
     @staticmethod
     def get_auth_cookies(user: User) -> dict[str, str]:
+        from superset.utils.auth_session_stamp import sync_session_auth_stamp_on_login
+
         # Login with the user specified to get the reports
         with app.test_request_context("/login"):
             login_user(user)
+            sync_session_auth_stamp_on_login(user)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Duplicate login hook call</b></div>
   <div id="fix">
   
   The call to `sync_session_auth_stamp_on_login` at line 129 duplicates logic already present in `security_manager.on_user_login` (line 935 in `security/manager.py`). However, `on_user_login` is dead code — the `user_logged_in` signal is never connected. This diff adds a workaround rather than fixing the root cause. The proper solution is to connect the signal or call `sm.on_user_login(user)` directly after `login_user`.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    --- superset/utils/machine_auth.py (lines 121-132) ---
    122:     @staticmethod
    123:     def get_auth_cookies(user: User) -> dict[str, str]:
    -        from superset.utils.auth_session_stamp import sync_session_auth_stamp_on_login
    -
    124:         # Login with the user specified to get the reports
    125:         with app.test_request_context("/login"):
    126:             login_user(user)
    -            sync_session_auth_stamp_on_login(user)
    +            # Call the security manager's on_user_login hook to ensure
    +            # consistent login behavior (audit logging, session stamping, etc.)
    +            from superset.extensions import security_manager
    +            security_manager.on_user_login(user)
    127:             # A mock response object to get the cookie information from
    128:             response = Response()
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3957e7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,199 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "scrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS: dict[str, str] = {
+    # TODO: native bcrypt/argon2 support is deferred to a follow-up SIP; see SIP-201.
+    # Keep legacy keys for backward compatibility; Werkzeug does not support
+    # bcrypt/argon2 directly in generate_password_hash.
+    "bcrypt": "scrypt",
+    "argon2": "scrypt",
+    "scrypt": "scrypt",
+    "pbkdf2": "pbkdf2:sha256",
+    "pbkdf2:sha256": "pbkdf2:sha256",
+}
+
+_COMMON_PASSWORDS = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides = app.config.get("AUTH_DB_CONFIG") or {}
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    """
+    return str(
+        get_merged_auth_db_config().get(
+            "login_rate_limit", AUTH_DB_DEFAULTS["login_rate_limit"]
+        )
+    )
+
+
+def get_public_auth_db_password_policy(cfg: dict[str, Any] | None = None) -> dict[str, Any]:
+    """
+    Return non-secret AUTH_DB password policy options for frontend validation UI.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    return {key: merged_cfg[key] for key in _PUBLIC_PASSWORD_POLICY_KEYS}

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing default fallback for KeyError</b></div>
   <div id="fix">
   
   If `AUTH_DB_CONFIG` lacks one of the public policy keys, this function raises `KeyError`. Other functions in this module (e.g., `get_auth_db_password_hash_method`, `validate_auth_db_password`) use `.get(key, AUTH_DB_DEFAULTS[key])` to safely fall back to defaults. Apply the same pattern here.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    --- a/superset/utils/auth_db_password.py
    +++ b/superset/utils/auth_db_password.py
    @@ -128,7 +128,7 @@ def get_public_password_policy() -> dict[str, Any]:
         cfg = get_merged_auth_db_config()
         merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
    -    return {key: merged_cfg[key] for key in _PUBLIC_PASSWORD_POLICY_KEYS}
    +    return {key: merged_cfg.get(key, AUTH_DB_DEFAULTS.get(key)) for key in _PUBLIC_PASSWORD_POLICY_KEYS}
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3957e7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/features/users/UserListResetPasswordModal.tsx:
##########
@@ -0,0 +1,219 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+import { getClientErrorObject, SupersetClient } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
+import { useToasts } from 'src/components/MessageToasts/withToasts';
+import {
+  FormModal,
+  FormItem,
+  Input,
+  Icons,
+  type FormInstance,
+} from '@superset-ui/core/components';
+import { GeneratePasswordInputSuffix } from 'src/components/GeneratePasswordInputSuffix';
+import AuthDbPasswordPolicyIndicator from 'src/components/AuthDbPasswordPolicyIndicator';
+import {
+  AUTH_DB_PASSWORD_MIN_LENGTH,
+  generateAuthDbPassword,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+import { UserObject } from 'src/pages/UsersList/types';
+import { buildSecurityUserUpdatePayload } from './utils';
+
+export interface UserListResetPasswordModalProps {
+  show: boolean;
+  onHide: () => void;
+  onSave: () => void;
+  user: UserObject | null;
+}
+
+function UserListResetPasswordModal({
+  show,
+  onHide,
+  onSave,
+  user,
+}: UserListResetPasswordModalProps) {
+  const { addDangerToast, addSuccessToast } = useToasts();
+  const getPasswordPolicyError = (password: string): string | null => {
+    const checks = getAuthDbPasswordPolicyChecks(password);
+    if (!checks.minLength) {
+      return t(
+        'Password must be at least %s characters long.',
+        AUTH_DB_PASSWORD_MIN_LENGTH,
+      );
+    }
+    if (!checks.uppercase) {
+      return t('Password must contain at least one uppercase letter.');
+    }
+    if (!checks.lowercase) {
+      return t('Password must contain at least one lowercase letter.');
+    }
+    if (!checks.digit) {
+      return t('Password must contain at least one digit.');
+    }
+    if (!checks.special) {
+      return t(
+        'Password must contain at least one special character (not a letter, digit, or space).',
+      );
+    }
+    if (!checks.commonPassword) {
+      return t('Password is too common.');
+    }
+    return null;
+  };
+
+  const handleFormSubmit = async (values: {
+    password: string;
+    confirmPassword: string;
+  }) => {
+    if (!user) {
+      return;
+    }
+    const { confirmPassword, ...rest } = values;
+    void confirmPassword;
+    const payload = {
+      ...buildSecurityUserUpdatePayload(user),
+      password: rest.password,
+    };
+    try {
+      await SupersetClient.put({
+        endpoint: `/api/v1/security/users/${user.id}`,
+        jsonPayload: payload,
+      });
+      addSuccessToast(
+        t('Password for %(username)s was updated successfully', {
+          username: user.username,
+        }),
+      );
+    } catch (error) {

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing callback breaks modal workflow</b></div>
   <div id="fix">
   
   Removing `onSave()` breaks the parent workflow in `UsersList/index.tsx`. The `onSave` callback (lines 575-579) handles `refreshData()`, `closeModal(ModalType.RESET_PASSWORD)`, and `setCurrentUser(null)`. Without this call, users will not see updated data, the modal won't close, and state won't reset after successful password changes.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3957e7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset/views/users/api.py:
##########
@@ -163,6 +235,177 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put_password",
+        log_to_statsd=False,
+    )
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies ``AUTH_DB_CONFIG``
+            policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+        try:
+            body = self.current_user_password_put_schema.load(request.json or {})
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            validate_auth_db_password(body["new_password"])
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        user_db = db.session.get(User, g.user.id)
+        if user_db is None:
+            return self.response_404()
+
+        old_hash = user_db.password
+        if not check_password_hash(old_hash, body["current_password"]):
+            return self.response_400(message="Incorrect current password.")
+
+        new_hash = generate_password_hash(
+            password=body["new_password"],
+            method=get_auth_db_password_hash_method(),
+            salt_length=app.config.get("FAB_PASSWORD_HASH_SALT_LENGTH", 16),
+        )

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>CWE-390: Unhandled Exception on Config Error</b></div>
   <div id="fix">
   
   Removed try/except ValidationError around `generate_password_hash` / `get_auth_db_password_hash_method`. The latter raises `marshmallow.ValidationError` when `password_hash_algorithm` is misconfigured. Without this handler, users get an unhandled 500 instead of a descriptive 400. Restore the exception handling or propagate the error explicitly. (See also: [CWE-390](https://cwe.mitre.org/data/definitions/390.html))
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    --- a/superset/views/users/api.py
    +++ b/superset/views/users/api.py
    @@ -301,10 +301,13 @@ class CurrentUserRestApi(BaseSupersetApi):
             if not check_password_hash(old_hash, body["current_password"]):
                 return self.response_400(message="Incorrect current password.")
    
    -        new_hash = generate_password_hash(
    -            password=body["new_password"],
    -            method=get_auth_db_password_hash_method(),
    -            salt_length=app.config.get("FAB_PASSWORD_HASH_SALT_LENGTH", 16),
    -        )
    +        try:
    +            new_hash = generate_password_hash(
    +                password=body["new_password"],
    +                method=get_auth_db_password_hash_method(),
    +                salt_length=app.config.get("FAB_PASSWORD_HASH_SALT_LENGTH", 16),
    +            )
    +        except ValidationError as error:
    +            return self.response_400(message=error.messages)
    
             try:
                 self.pre_update(g.user, {})
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3957e7</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4769506286

   Pillar 1 done. I am just waiting for these AI reviewers to review it. Else we can continue. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5012107106

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3610463922


##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -112,9 +112,9 @@ def downgrade() -> None:
     session = Session(bind=bind)
     do_downgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction

Review Comment:
   Fixed: same change applied to downgrade() -- flush only, no manual commit/rollback, so it stays inside Alembic's transaction like upgrade(). Resolving.



##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -101,9 +101,9 @@ def upgrade() -> None:
     session = Session(bind=bind)
     do_upgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction
     except SQLAlchemyError as ex:
-        session.rollback()
+        session.rollback()  # pylint: disable=consider-using-transaction

Review Comment:
   Addressed differently: rather than switching to superset.migrations.shared.utils helpers, we removed the manual session.commit()/rollback() entirely so the migration session stays inside Alembic's own transaction. Resolving.



##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -112,9 +112,9 @@ def downgrade() -> None:
     session = Session(bind=bind)
     do_downgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction
     except SQLAlchemyError as ex:
-        session.rollback()
+        session.rollback()  # pylint: disable=consider-using-transaction

Review Comment:
   Addressed differently, same as upgrade(): removed the manual session.commit()/rollback() in downgrade() so it stays inside Alembic's own transaction. Resolving.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5020501687

   @rusackas I have resolved almost all bot comments (remaining I am validating) and security issues. 
   
   - I found a solution for that using fake validation with fake validation to mimic similar execution time. 
   - EnxDev's question about remember-me sessions is resolved now if auth stamp is missing it will simply invalidate the session instead of copying it from database to the user. 
   
   I hope that addresses both of your concerns. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4367299542

   Hi @shantanukhond, sharing notes from the SIP-author perspective focused on alignment rather than the implementation, which the prior review covers thoroughly. 
   
   +1 in particular to the session-clear-before-commit, atomicity, and rate-limiting points raised there.
   
   **Scope suggest splitting this PR**
   
   The change currently bundles Pillar 1 (authenticated password change) with parts of Pillar 3 (admin reset). 
   Given the XXL size, splitting would help reviewers and let the pillars progress independently:
   1. This PR → Pillar 1 + `auth_audit_log` foundation
   2. New PR → Pillar 3 admin reset
   
   **Pillar 3 — design alignment needed**
   
   The admin "set password directly" flow conflicts with the SIP. 
   
   Per *Rejected Alternative #6*, admins should trigger an **email reset link** rather than ever seeing or setting the user's password (zero-knowledge principle, since admins can leave or change roles). 
   The Pillar 3 PR should be reworked around the SMTP-based flow.
   
   **Pillar 1 — items still missing vs. the SIP**
   - **Multi-session invalidation** across all active sessions for the user, not only the current one.
   - **`password_hash_algorithm` config** (bcrypt/argon2) — can be deferred with a TODO if we want to keep the PR tight.
   
   I suggest reading the linked SIP carefully before further development.
   
   Happy to track these on the SIP side as the work progresses.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3655697928


##########
tests/unit_tests/migrations/test_cleanup_stale_can_import_pvm_transaction.py:
##########
@@ -0,0 +1,126 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Tests for migration ``a7d3f1b9c2e4_cleanup_stale_can_import_pvm``'s transaction
+handling: ``upgrade()``/``downgrade()`` must flush, not commit, so the migration's
+session stays inside Alembic's own transaction instead of committing early.
+"""
+
+from importlib import import_module
+from unittest.mock import patch
+
+import pytest
+from sqlalchemy import create_engine
+from sqlalchemy.orm import Session
+
+from superset.migrations.shared.security_converge import (
+    _add_permission,
+    _add_permission_view,
+    _add_view_menu,
+    _find_pvm,
+    Base,
+    Role,
+)
+
+migration = import_module(
+    "superset.migrations.versions."
+    "2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm"
+)
+
+VIEW = "ImportExportRestApi"
+STALE_PERM = "can_import"  # no trailing underscore
+LIVE_PERM = "can_import_"  # trailing underscore (the real permission)
+
+
+@pytest.fixture
+def engine():
+    engine = create_engine("sqlite:///:memory:")
+    Base.metadata.create_all(engine)
+    return engine
+
+
+def _seed_stale_pvm(session: Session) -> None:
+    view_menu = _add_view_menu(session, VIEW)
+    stale_permission = _add_permission(session, STALE_PERM)
+    stale_pvm = _add_permission_view(session, stale_permission, view_menu)
+    role = Role(name="stale_import_role")
+    role.permissions.append(stale_pvm)
+    session.add(role)
+    session.commit()
+
+
+def test_upgrade_flushes_without_committing(engine) -> None:
+    """upgrade() must not call session.commit(). The migration session shares
+    Alembic's connection, so an explicit commit there would end the outer
+    transaction early and let the permission changes land even if a later step
+    (including Alembic's own revision stamp write) fails."""
+    with Session(engine) as seed:
+        _seed_stale_pvm(seed)
+
+    with engine.begin() as conn:
+        with (
+            patch.object(migration, "op") as mock_op,
+            patch.object(Session, "commit") as mock_commit,
+        ):
+            mock_op.get_bind.return_value = conn
+            migration.upgrade()
+        mock_commit.assert_not_called()

Review Comment:
   **Suggestion:** The test only verifies that `commit()` was not called; it never verifies that the migration explicitly flushes its changes. Queries performed by the migration or later verification can trigger SQLAlchemy autoflush, allowing an implementation with no explicit `flush()` to pass while still violating the transaction-handling contract described by the test. Patch and assert `Session.flush()` as well, or otherwise make the test require an explicit flush. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Migration tests can miss regressions in transaction sequencing.
   - ⚠️ Autoflush-dependent migrations may behave inconsistently across database backends.
   - ❌ Failed migration steps may be harder to roll back predictably.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run `test_upgrade_flushes_without_committing()` in
   `tests/unit_tests/migrations/test_cleanup_stale_can_import_pvm_transaction.py:65-87`; it
   patches only `Session.commit()` at lines 75-80.
   
   2. The test invokes `migration.upgrade()` from the dynamically imported migration at lines
   38-41, but does not patch or assert `Session.flush()`.
   
   3. Change the migration implementation so it omits an explicit `flush()` while retaining
   database queries that can trigger SQLAlchemy autoflush; the test still reaches
   `mock_commit.assert_not_called()` successfully.
   
   4. The outer transaction at lines 73-82 can then commit the changes, and the verification
   queries at lines 85-87 pass, despite the test never proving that `upgrade()` explicitly
   flushed before completing.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f7753e8e02f7462fac70a141dfcd6dfa&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f7753e8e02f7462fac70a141dfcd6dfa&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/migrations/test_cleanup_stale_can_import_pvm_transaction.py
   **Line:** 75:80
   **Comment:**
   	*Possible Bug: The test only verifies that `commit()` was not called; it never verifies that the migration explicitly flushes its changes. Queries performed by the migration or later verification can trigger SQLAlchemy autoflush, allowing an implementation with no explicit `flush()` to pass while still violating the transaction-handling contract described by the test. Patch and assert `Session.flush()` as well, or otherwise make the test require an explicit flush.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=1666b3cec73a04c71549a6caa675264ee87e4e1341ba6c03f839029f80265723&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=1666b3cec73a04c71549a6caa675264ee87e4e1341ba6c03f839029f80265723&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,268 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS: tuple[str, ...] = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS: frozenset[str] = frozenset({"bcrypt", "argon2"})
+
+# bcrypt silently truncates input beyond this UTF-8 byte limit.
+BCRYPT_MAX_PASSWORD_BYTES: int = 72
+
+_COMMON_PASSWORDS: frozenset[str] = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides: Any = app.config.get("AUTH_DB_CONFIG") or {}
+    if not isinstance(overrides, Mapping):
+        raise ValidationError(
+            {
+                "AUTH_DB_CONFIG": [
+                    "Invalid AUTH_DB_CONFIG. Expected a mapping of policy options."
+                ]
+            }
+        )
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    Falls back to the default when the configured value isn't a usable string (for
+    example ``None`` or a malformed override), so a misconfigured ``AUTH_DB_CONFIG``
+    can't break rate limiting at request time.
+    """
+    default = str(AUTH_DB_DEFAULTS["login_rate_limit"])
+    raw = get_merged_auth_db_config().get("login_rate_limit", default)
+    if isinstance(raw, str) and raw.strip():
+        return raw

Review Comment:
   **Suggestion:** The implementation returns every non-empty string unchanged, even when it is not a valid Flask-Limiter rate-limit expression. The docstring promises malformed overrides fall back to the default, but malformed values can reach the limiter and cause request-time rate-limit parsing failures. Parse or validate the configured expression before returning it, and use the default when parsing fails. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ AUTH_DB password-change requests can fail with invalid configuration.
   - ⚠️ Rate limiting may be unavailable for password verification.
   - ⚠️ Operator configuration errors surface at request time.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset's `AUTH_DB_CONFIG` with a non-empty but invalid `login_rate_limit`
   string, such as `"not-a-limiter-expression"`, which is read by
   `get_merged_auth_db_config()` at `superset/utils/auth_db_password.py:104-115`.
   
   2. Invoke `get_auth_db_login_rate_limit_string()` at
   `superset/utils/auth_db_password.py:118`, which is used by the AUTH_DB
   password-verification rate-limiting setup for `PUT /api/v1/me/password`.
   
   3. The function reaches `superset/utils/auth_db_password.py:129-130`; because the value is
   a non-empty string, it returns the malformed expression instead of the documented default.
   
   4. When Flask-Limiter parses or applies that expression for the password endpoint,
   rate-limit configuration parsing can fail, preventing password-verification requests from
   being handled normally.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7e84928bede041b68c6e12ed6b889579&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=7e84928bede041b68c6e12ed6b889579&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 129:130
   **Comment:**
   	*Logic Error: The implementation returns every non-empty string unchanged, even when it is not a valid Flask-Limiter rate-limit expression. The docstring promises malformed overrides fall back to the default, but malformed values can reach the limiter and cause request-time rate-limit parsing failures. Parse or validate the configured expression before returning it, and use the default when parsing fails.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b2ec836d53555c5965678f66556f053fd8cc3756e3bc3dddd8a2e0d7d58041e0&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b2ec836d53555c5965678f66556f053fd8cc3756e3bc3dddd8a2e0d7d58041e0&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5003721420

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "sfirke (via GitHub)" <gi...@apache.org>.
sfirke commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4298766618

   I see you referenced SIP 201: https://github.com/apache/superset/issues/37927 thanks for connecting the dots there and referencing an agreed-upon plan! Did you coordinate this work with @EnxDev who wrote that SIP? I wonder if they are working on this too?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3520040608


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,392 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register login and request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    from flask_login import user_logged_in
+
+    if not getattr(app, "superset_session_auth_stamp_login_connected", False):
+        app.superset_session_auth_stamp_login_connected = True
+
+        @user_logged_in.connect
+        def _sync_stamp_after_login(sender: Flask, user: Any, **extra: Any) -> None:  # noqa: ARG001
+            """Copy the DB stamp into the session after Flask-Login finalizes it."""
+            sync_session_auth_stamp_on_login(user)
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing tests for new public function</b></div>
   <div id="fix">
   
   The new public function `cache_user_session_auth_stamp` introduced at line 177 is not covered by tests. Per rule [11730], comprehensive unit tests should be added for new tools covering success paths, error scenarios, and edge cases. Add the import to test_auth_session_stamp.py and verify the cache write behavior.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #5103d3</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3596011015


##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -86,30 +159,90 @@ function UserInfoModal({
     </>
   );
 
-  const ResetPasswordFields = () => (
+  const ResetPasswordFields = ({ form }: { form: FormInstance }) => (
     <>
       <FormItem
-        name="password"
-        label={t('Password')}
-        rules={[{ required: true, message: t('Password is required') }]}
+        name="current_password"
+        label={t('Current password')}
+        rules={[{ required: true, message: t('Current password is required') }]}
       >
         <Input.Password
-          name="password"
-          placeholder={t("Enter the user's password")}
+          name="current_password"
+          autoComplete="current-password"
+          placeholder={t('Enter your current password')}
         />
       </FormItem>
+      <FormItem
+        name="new_password"
+        label={t('New password')}
+        rules={[
+          { required: true, message: t('New password is required') },
+          {
+            validator(_, value) {
+              const password = String(value ?? '');
+              if (!password) {
+                return Promise.resolve();
+              }
+              const errorMessage = getPasswordPolicyError(password);
+              return errorMessage
+                ? Promise.reject(new Error(errorMessage))
+                : Promise.resolve();
+            },
+          },
+        ]}
+      >
+        <Input.Password
+          name="new_password"
+          autoComplete="new-password"
+          placeholder={t('Enter a new password')}
+          suffix={
+            <GeneratePasswordInputSuffix
+              onGenerate={() => {
+                const pwd = generateAuthDbPassword(passwordPolicy);
+                form.setFieldsValue({
+                  new_password: pwd,
+                  confirm_password: pwd,
+                });
+                // setFieldsValue does not fire the form's change handlers, so
+                // validate the affected fields to recompute submit state.
+                form
+                  .validateFields(['new_password', 'confirm_password'])
+                  .catch(() => {});
+              }}
+            />

Review Comment:
   **Suggestion:** The password generator call can throw (for example when server policy is impossible to satisfy, such as bcrypt with a minimum length above 72 bytes), but this click handler does not catch that exception. That will surface as an uncaught runtime error and break the modal instead of showing a user-facing validation/toast message. Catch generator failures in this handler and report a friendly error without crashing the form. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ❌ Password generator click can crash reset password modal.
   ⚠️ Users lose feedback when policy or crypto misconfigured.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the password generator UI wiring in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:176-215`. On lines 198-213, the
   `Input.Password` suffix renders `GeneratePasswordInputSuffix` with `onGenerate={() => {
   const pwd = generateAuthDbPassword(passwordPolicy); ... }}` (lines 201-212).
   
   2. Inspect the generator implementation at
   `superset-frontend/src/utils/generateAuthDbPassword.ts:286-310`.
   `generateAuthDbPassword()` (lines 290-309) calls helpers that can throw:
   
      - `secureRandomInt()` (lines 139-148) throws if `globalThis.crypto.getRandomValues` is
      unavailable.
   
      - The generator loop throws `new Error('generateAuthDbPassword: exhausted retries')` at
      line 309 if no password can satisfy the supplied `AuthDbPasswordPolicy` (e.g., an
      impossible combination of minimum length and bcrypt byte limit).
   
   3. Follow the user-facing path: the UserInfo page at
   `superset-frontend/src/pages/UserInfo/index.tsx:91-269` opens `ChangePasswordModal` (lines
   245-253), which renders `UserInfoModal` with `isEditMode={false}`. This causes
   `ResetPasswordFields` (lines 162-260) to be shown, including the generator button wired to
   the `onGenerate` handler above.
   
   4. In an environment without Web Crypto (`globalThis.crypto.getRandomValues` missing) or
   under a misconfigured password policy that cannot be satisfied, clicking the "Generate
   password" button (rendered by `GeneratePasswordInputSuffix` in
   `superset-frontend/src/components/GeneratePasswordInputSuffix.tsx:29-48`) will propagate
   the exception from `generateAuthDbPassword()` through `onGenerate` without any try/catch.
   React treats this as an uncaught error during the click event, which can break the modal
   or hit a global error boundary instead of displaying a toast or validation error,
   confirming the lack of defensive handling on this path.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=6429e61bdb484492864de314c8e70a48&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=6429e61bdb484492864de314c8e70a48&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 201:212
   **Comment:**
   	*Possible Bug: The password generator call can throw (for example when server policy is impossible to satisfy, such as bcrypt with a minimum length above 72 bytes), but this click handler does not catch that exception. That will surface as an uncaught runtime error and break the modal instead of showing a user-facing validation/toast message. Catch generator failures in this handler and report a friendly error without crashing the form.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f88cecb1ba38705d024d3176bd87a949a90992b891536417f1bce05fd51a1e02&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f88cecb1ba38705d024d3176bd87a949a90992b891536417f1bce05fd51a1e02&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -36,10 +50,51 @@ function UserInfoModal({
   user,
 }: UserInfoModalProps) {
   const { addDangerToast, addSuccessToast } = useToasts();
+  const [passwordPolicy, setPasswordPolicy] = useState<AuthDbPasswordPolicy>(
+    AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  );
+
+  useEffect(() => {
+    if (!show || isEditMode) {
+      return;
+    }
+    let ignore = false;
+    SupersetClient.get({
+      endpoint: '/api/v1/me/password/policy',
+    })
+      .then(({ json }) => {
+        if (!ignore && json?.result) {
+          setPasswordPolicy(json.result as AuthDbPasswordPolicy);
+        }
+      })
+      .catch(() => {
+        // Keep default policy when endpoint is unavailable.
+      });
+    return () => {
+      ignore = true;
+    };
+  }, [show, isEditMode]);
+
+  const getPasswordPolicyError = (password: string): string | null =>
+    getAuthDbPasswordPolicyError(password, passwordPolicy);
+
+  const getSubmitErrorMessage = async (error: unknown): Promise<string> => {
+    const clientError = await getClientErrorObject(
+      error as Parameters<typeof getClientErrorObject>[0],
+    );
+    const raw = clientError.error ?? clientError.message;
+    if (typeof raw === 'string' && raw) {
+      return raw;
+    }
+    if (raw && typeof raw === 'object') {
+      return (Object.values(raw).flat() as string[]).join(' ');
+    }
+    return t('Something went wrong while saving the user info');

Review Comment:
   **Suggestion:** This fallback error message is shown for both profile edit and password-change submissions, but it explicitly says “saving the user info,” which is incorrect for password reset failures and will mislead users. Use an operation-appropriate fallback message for the password flow. [comment mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Minor 🧹</summary>
   
   ```mdx
   ⚠️ Password reset errors misreported as user info issues.
   ⚠️ Can confuse users diagnosing password change failures.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Inspect the shared error-message helper in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:81-93`.
   `getSubmitErrorMessage()` falls back at line 92 to `t('Something went wrong while saving
   the user info')` when neither `clientError.error` nor `clientError.message` provides a
   usable string.
   
   2. Examine the form submission handler in the same file at `handleFormSubmit()` on lines
   104-137. For edit mode (lines 106-112), it PUTs `/api/v1/me/` to update profile info; for
   password reset (lines 113-133), it PUTs `/api/v1/me/password` to change the password and
   re-authenticates the session.
   
   3. Note the shared catch block on lines 134-137: `catch (error) { addDangerToast(await
   getSubmitErrorMessage(error)); throw error; }`. This is used for both the profile edit
   flow and the password-change flow, meaning the fallback string from step 1 is shown on any
   generic failure regardless of which endpoint failed.
   
   4. Trigger a generic backend or network error on the password-change endpoint (e.g., cause
   a 500 or a timeout so that `getClientErrorObject()` yields no specific message). The
   password reset submission from the ChangePasswordModal (wired in
   `superset-frontend/src/pages/UserInfo/index.tsx:245-253`) results in a toast with the
   fallback `Something went wrong while saving the user info`, which inaccurately describes a
   password-change failure as a user-info save issue and can mislead users diagnosing
   password problems.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=81b953e4df44428d9f1dc9364a217398&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=81b953e4df44428d9f1dc9364a217398&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 92:92
   **Comment:**
   	*Comment Mismatch: This fallback error message is shown for both profile edit and password-change submissions, but it explicitly says “saving the user info,” which is incorrect for password reset failures and will mislead users. Use an operation-appropriate fallback message for the password flow.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=376f8b7811269a2b97a22577bf0f2957684c792e32a468db300ea3fd6bbd3604&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=376f8b7811269a2b97a22577bf0f2957684c792e32a468db300ea3fd6bbd3604&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3595975685


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,265 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS: tuple[str, ...] = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS: frozenset[str] = frozenset({"bcrypt", "argon2"})
+
+# bcrypt silently truncates input beyond this UTF-8 byte limit.
+BCRYPT_MAX_PASSWORD_BYTES: int = 72
+
+_COMMON_PASSWORDS: frozenset[str] = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides: Any = app.config.get("AUTH_DB_CONFIG") or {}
+    if not isinstance(overrides, Mapping):
+        raise ValidationError(
+            {
+                "AUTH_DB_CONFIG": [
+                    "Invalid AUTH_DB_CONFIG. Expected a mapping of policy options."
+                ]
+            }
+        )
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    """
+    return str(
+        get_merged_auth_db_config().get(
+            "login_rate_limit", AUTH_DB_DEFAULTS["login_rate_limit"]
+        )
+    )
+
+
+def get_public_auth_db_password_policy(
+    cfg: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+    """
+    Return non-secret AUTH_DB password policy options for frontend validation UI.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()

Review Comment:
   **Suggestion:** Add an explicit type annotation for this local config variable so it complies with the required type-hint coverage. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is new Python code in the added file, and the local variable `merged_cfg` is assigned a config mapping without an explicit type annotation even though it can be annotated. That matches the type-hint requirement.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=2b86701a1b324a76a98e18b400574fee&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=2b86701a1b324a76a98e18b400574fee&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 137:137
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for this local config variable so it complies with the required type-hint coverage.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6bab49b7fc63cb8676fdf3aab746ad3297cb44f7d0528d4594a7f0deb3697013&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6bab49b7fc63cb8676fdf3aab746ad3297cb44f7d0528d4594a7f0deb3697013&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4372028228

   @EnxDev Thank you so much for your guidance. I will try to finish first part i.e. Pillar 1 by this week. I will keep you posted. I will also go through the SIP. Thank you. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4896628917

   ## EnxDev's Review Agent — apache/superset#39469 · HEAD 5e2a6bc
   **request changes** — the new password endpoint writes plaintext passwords to the audit `logs` table on every change.
   
   _Supersedes my [prior review](https://github.com/apache/superset/pull/39469#issuecomment-4866465991) (HEAD 3ff511c). Both earlier blockers are fixed in this revision: the doubled `UserLoggedIn` audit event (`auth_user_db` is now a clean standalone override with no explicit `on_user_login` call — single event confirmed by `test_auth_user_db_logs_single_user_logged_in_event`) and the cache-before-commit race (`bump_user_session_auth_stamp` no longer writes the cache; `cache_user_session_auth_stamp` runs post-commit, guarded by `test_bump_user_session_auth_stamp_does_not_write_cache`). Verified `get_first_user`/`noop_user_update` exist on the FAB base and FAB invokes `on_user_login` exactly once._
   
   ### 🔴 Functional
   - **`superset/views/users/api.py:318`** · _High_ — `update_my_password` is decorated with `@event_logger.log_this_with_context`. On every call, `AbstractEventLogger.log_context` runs `collect_request_payload()` (`superset/utils/log.py:40`), which merges `request.get_json()` verbatim into the payload, then passes it as `records=[payload]` to the logger. The default `EVENT_LOGGER = DBEventLogger()` `json.dumps`-es each record into `Log.json` — so `current_password`, `new_password`, and `confirm_password` land in the metadata `logs` table in **plaintext**, on both success and failure paths. `curate_payload` does not apply here (it only guards `curated_payload`, which `DBEventLogger` uses only when `records` is empty — it isn't). This contradicts the guarantee added in this same PR (`configuring-superset.mdx`: "they are never stored in plaintext or logged"). Principal: any role with `can_read on Log` — Admin by default, but log read is commonly granted to custom auditor roles
 , and the `logs` table is routinely shipped to external SIEMs / backups. Leaking `current_password` in particular hands them a credential users very likely reuse on non-Superset systems, beyond the "Admin manages accounts" capability. **Fix:** drop `log_this_with_context` on this endpoint and emit a manual audit event carrying only `{user_id, action}` (no body), or scrub password-like keys in `collect_request_payload`. **regression test:** after a successful `PUT /me/password`, assert no `Log` row's `json` contains the new or current password.
   
   ### 🟡 Should-fix
   - **`superset/security/manager.py:1161`** (`reset_password`) and **`superset/cli/reset.py`** — only the self-service path (`_commit_user_password_change`) calls `bump_user_session_auth_stamp`. Admin-initiated resets and the CLI don't rotate the stamp, so an admin resetting a *compromised* account (the case that matters most) leaves the attacker's existing session valid. If out of scope for Pillar 1, note it as a follow-up; otherwise call `bump_user_session_auth_stamp(user_id)` from `reset_password` too.
   
   ### 🔵 Nits
   - **`superset/security/manager.py:1212`** + the `user_logged_in` signal handler in `auth_session_stamp.py` — `sync_session_auth_stamp_on_login` fires twice per login (once from `on_user_login`, once from the signal), each an `@transaction()` commit + cache write. Idempotent but redundant; keep one path.
   - PR title: the breaking `PUT /api/v1/me/` password rejection warrants `feat(auth)!:` per Conventional Commits (the `risk:breaking-change` label is set; the `!` is not).
   - `2026-06-23...a7d3f1b9c2e4` migration is edited only to add `# pylint: disable` comments to an already-merged file — harmless but unusual.
   
   ### 🙌 Praise
   - bcrypt's 72-byte truncation is handled at the hash, verify, and policy layers with a dedicated `test_bcrypt_verify_rejects_distinct_passwords_equal_after_truncation` — closes the classic truncation-collision auth bypass.
   - The optimistic-locking UPDATE (`WHERE password == old_hash` → `PasswordChangeConflictError`) with the post-commit cache write cleanly resolves the prior revision's cache-before-commit race.
   
   <!-- enxdev-review-agent:5e2a6bc -->
   _Reviewed by EnxDev's Review Agent — @EnxDev · HEAD 5e2a6bc._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4894338834

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4753089415

   I resolved several of the obsolete bot review comments, but there are a lot of review comments here you can mark as resolved if they indeed are. Waiting on one response to my last note: should we land just the `/me/password` change here and split the audit-log infra + admin reset into a follow-up? Also, it seems we should drop the new `auth_audit_log` table in favor of the existing `Log` (per @dpgaspar).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4753452240

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3425682720


##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,163 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { css, styled } from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import { Icons, Popover, Progress, Typography } from '@superset-ui/core/components';
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`
+    flex: 1;
+    cursor: help;
+
+    .ant-progress {
+      margin-bottom: 0;
+    }
+  `}
+`;
+
+const Checklist = styled.div`
+  ${({ theme }) => css`
+    min-width: ${theme.sizeUnit * 70}px;
+    display: flex;
+    flex-direction: column;
+    gap: ${theme.sizeUnit}px;
+  `}
+`;
+
+const ChecklistItem = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+    font-size: ${theme.fontSize}px;
+  `}
+`;
+
+const requirementText = {
+  minLength: (minLength: number) => t('At least %s characters', minLength),
+  uppercase: t('Contains an uppercase letter'),
+  lowercase: t('Contains a lowercase letter'),
+  digit: t('Contains a digit'),
+  special: t('Contains a special character'),
+  commonPassword: t('Is not a common password'),
+};
+
+function getStrengthState(passedChecks: number) {
+  if (passedChecks <= 2) {
+    return { color: '#cf1322', label: t('Very weak') };
+  }
+  if (passedChecks === 3) {
+    return { color: '#d46b08', label: t('Weak') };
+  }
+  if (passedChecks === 4) {
+    return { color: '#d4b106', label: t('Medium') };
+  }
+  if (passedChecks === 5) {
+    return { color: '#389e0d', label: t('Strong') };
+  }
+  return { color: '#08979c', label: t('Very strong') };
+}
+
+export default function AuthDbPasswordPolicyIndicator({
+  password,
+  policy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+}: AuthDbPasswordPolicyIndicatorProps) {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  const hasPassword = password.length > 0;
+  const checklist = [
+    {
+      label: requirementText.minLength(policy.password_min_length),
+      passed: hasPassword && checks.minLength,
+    },
+    ...(policy.password_require_uppercase
+      ? [{ label: requirementText.uppercase, passed: hasPassword && checks.uppercase }]
+      : []),
+    ...(policy.password_require_lowercase
+      ? [{ label: requirementText.lowercase, passed: hasPassword && checks.lowercase }]
+      : []),
+    ...(policy.password_require_digit
+      ? [{ label: requirementText.digit, passed: hasPassword && checks.digit }]
+      : []),
+    ...(policy.password_require_special
+      ? [{ label: requirementText.special, passed: hasPassword && checks.special }]
+      : []),
+    ...(policy.password_common_list_check
+      ? [
+          {
+            label: requirementText.commonPassword,
+            passed: hasPassword && checks.commonPassword,
+          },
+        ]
+      : []),
+  ];
+  const passedChecks = checklist.filter(item => item.passed).length;
+  const percent = Math.round((passedChecks / checklist.length) * 100);
+  const strength = getStrengthState(passedChecks);

Review Comment:
   **Suggestion:** Strength labeling is computed from the absolute number of passed checks, not from the ratio of passed-to-enabled rules, so policies with fewer enabled requirements are misclassified (for example 100% pass can still show "Very weak"). Derive strength from `percent` (or normalize by `checklist.length`) to keep labels consistent with configurable policy settings. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Password strength label misleading under relaxed AUTH_DB policies.
   - ⚠️ Users may distrust password-strength feedback in reset modal.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset with AUTH_DB enabled and a relaxed AUTH_DB_CONFIG so that only
   `password_min_length` is enforced (all `password_require_*` and
   `password_common_list_check` flags false) using the documented AUTH_DB_CONFIG options in
   `docs/admin_docs/configuration/configuring-superset.mdx:295-299`.
   
   2. Start Superset, log in with AUTH_DB, and navigate to the "Your user information" page
   implemented in `superset-frontend/src/pages/UserInfo/index.tsx:91-125`, where `authType
   === AuthType.AuthDB` enables the "Reset my password" button in `SubMenuButtons` at lines
   126-149.
   
   3. Click "Reset my password" to open `ChangePasswordModal` from
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:276-283`, which renders
   `ResetPasswordFields` (lines 167-257) and includes a "Password strength" FormItem that
   mounts `AuthDbPasswordPolicyIndicator` (lines 213-227).
   
   4. In the "New password" field, enter a password that satisfies the active relaxed policy
   so that `getAuthDbPasswordPolicyChecks` in `AuthDbPasswordPolicyIndicator.tsx:99-126`
   returns `true` for all enabled checks; `passedChecks` then equals `checklist.length`,
   `percent` computes to 100 at line 128, but `getStrengthState(passedChecks)` at line 129
   classifies values `<= 2` as "Very weak" (lines 79-82), so with only one or two enabled
   checks the indicator shows a 100% progress bar labeled "Very weak" despite the user fully
   satisfying the configured policy.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d71a99c70f284af18067df28710a7bed&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d71a99c70f284af18067df28710a7bed&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx
   **Line:** 127:129
   **Comment:**
   	*Logic Error: Strength labeling is computed from the absolute number of passed checks, not from the ratio of passed-to-enabled rules, so policies with fewer enabled requirements are misclassified (for example 100% pass can still show "Very weak"). Derive strength from `percent` (or normalize by `checklist.length`) to keep labels consistent with configurable policy settings.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=11f196c85143fd335c83ea6f6b983f101de24432737ecdcea4c4f333ad2d59d6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=11f196c85143fd335c83ea6f6b983f101de24432737ecdcea4c4f333ad2d59d6&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -86,30 +164,82 @@ function UserInfoModal({
     </>
   );
 
-  const ResetPasswordFields = () => (
+  const ResetPasswordFields = ({ form }: { form: FormInstance }) => (
     <>
       <FormItem
-        name="password"
-        label={t('Password')}
-        rules={[{ required: true, message: t('Password is required') }]}
+        name="current_password"
+        label={t('Current password')}
+        rules={[{ required: true, message: t('Current password is required') }]}
       >
         <Input.Password
-          name="password"
-          placeholder={t("Enter the user's password")}
+          name="current_password"
+          autoComplete="current-password"
+          placeholder={t('Enter your current password')}
         />
       </FormItem>
+      <FormItem
+        name="new_password"
+        label={t('New password')}
+        rules={[
+          { required: true, message: t('New password is required') },
+          {
+            validator(_, value) {
+              const password = String(value ?? '');
+              if (!password) {
+                return Promise.resolve();
+              }
+              const errorMessage = getPasswordPolicyError(password);
+              return errorMessage
+                ? Promise.reject(new Error(errorMessage))
+                : Promise.resolve();
+            },
+          },
+        ]}
+      >
+        <Input.Password
+          name="new_password"
+          autoComplete="new-password"
+          placeholder={t('Enter a new password')}
+          suffix={
+            <GeneratePasswordInputSuffix
+              onGenerate={() => {
+                const pwd = generateAuthDbPassword();
+                form.setFieldsValue({ new_password: pwd, confirm_password: pwd });

Review Comment:
   **Suggestion:** The generated password ignores the fetched `passwordPolicy` and always uses the default generator rules, so in deployments with stricter `AUTH_DB_CONFIG` (for example longer minimum length) the "Generate password" action can produce a value that immediately fails validation and cannot be submitted. Generate passwords against the active policy (or re-generate until `getPasswordPolicyError` returns null) before setting form fields. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Generated passwords rejected under stricter AUTH_DB policies.
   - ⚠️ Reset password flow less usable for hardened deployments.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. On the server, configure a stricter AUTH_DB_CONFIG (for example `password_min_length =
   16`) so that `get_public_auth_db_password_policy()` returns this longer minimum; this
   policy is exposed by `CurrentUserRestApi.get_my_password_policy` at
   `superset/views/users/api.py:20-53`, which serves `GET /api/v1/me/password/policy`.
   
   2. Start Superset with `AUTH_TYPE = AUTH_DB`, log in, and open the "Your user information"
   page in `superset-frontend/src/pages/UserInfo/index.tsx:91-125`, where `authType ===
   AuthType.AuthDB` and the "Reset my password" SubMenu button (lines 126-149) opens the
   change-password flow.
   
   3. Click "Reset my password" to open `ChangePasswordModal` from
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:276-283`; on mount, the modal
   fetches the active password policy via `SupersetClient.get({ endpoint:
   '/api/v1/me/password/policy' })` in the `useEffect` at lines 58-73 and stores it in
   `passwordPolicy` (lines 54-56), so `passwordPolicy.password_min_length` reflects the
   stricter server value (e.g., 16).
   
   4. In the reset modal, click the "Generate password" suffix attached to the "New password"
   input in `ResetPasswordFields` (lines 167-212); the handler at lines 204-207 calls
   `generateAuthDbPassword()` from
   `superset-frontend/src/utils/generateAuthDbPassword.ts:162-179`, which always generates a
   password targeting the default 12-character `AUTH_DB_DEFAULT_PASSWORD_POLICY` (lines 24,
   34-41), then sets it into `new_password` and `confirm_password` via `form.setFieldsValue`
   (line 207). When the FormItem validator for "New password" runs at lines 181-197 using
   `getPasswordPolicyError` (lines 75-101), `checks.minLength` fails because the generated
   password length (12) is less than `passwordPolicy.password_min_length` (16), so the UI
   shows "Password must be at least %s characters long." and rejects submission,
   demonstrating that the generator ignores the active policy and can produce passwords that
   cannot be submitted under custom AUTH_DB settings.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=eb0bfefb62ea45979d9db794d02508b1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=eb0bfefb62ea45979d9db794d02508b1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 206:207
   **Comment:**
   	*Api Mismatch: The generated password ignores the fetched `passwordPolicy` and always uses the default generator rules, so in deployments with stricter `AUTH_DB_CONFIG` (for example longer minimum length) the "Generate password" action can produce a value that immediately fails validation and cannot be submitted. Generate passwords against the active policy (or re-generate until `getPasswordPolicyError` returns null) before setting form fields.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=4fb21420f154fd69446d04375915f5942aeaee4e1ea318d9c5ecb32c2f190aab&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=4fb21420f154fd69446d04375915f5942aeaee4e1ea318d9c5ecb32c2f190aab&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4726421722

   Working on fixing issue raised by bots. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4790224976

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3474424037


##########
superset/views/users/api.py:
##########
@@ -163,6 +302,132 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put_password",
+        log_to_statsd=False,
+    )
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:

Review Comment:
   **Suggestion:** This new password-change route is not integrated with the forced-password-change enforcement allowlist, so users flagged with `password_must_change` will be redirected by the global before-request hook instead of reaching this API. That breaks the intended self-service flow exactly for first-login users who must change their password. Add this endpoint to the exempt password-change routes (or equivalent bypass logic) so flagged users can call it. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Forced-change users blocked from /api/v1/me/password endpoint.
   - ⚠️ First-login self-service password change flow broken.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Application startup registers the forced-password-change before-request hook via
   `register_request_handlers()` in `superset/initialization/__init__.py:6-16`, which calls
   `register_password_change_enforcement(self.superset_app)` from
   `superset/security/password_change.py:148-156`.
   
   2. The enforcement hook `_enforce_password_change()` in
   `superset/security/password_change.py:155-175` checks
   `_is_exempt_endpoint(request.endpoint)` (lines 133-145) against `_EXEMPT_VIEW_CLASSES` and
   `_EXEMPT_ENDPOINTS` (lines 51-66). `CurrentUserRestApi` is not listed in
   `_EXEMPT_VIEW_CLASSES`, and its endpoints (e.g. `CurrentUserRestApi.update_my_password`)
   are not in `_EXEMPT_ENDPOINTS`.
   
   3. A user is provisioned with `password_must_change = True` (column defined in
   `superset/models/user_attributes.py:60-64` and set via `set_password_must_change()` in
   `superset/security/password_change.py:95-123`). With `ENABLE_FORCE_PASSWORD_CHANGE`
   enabled (checked at `password_change.py:163-164`), `password_change_required(user)`
   returns True (lines 86-92).
   
   4. The same authenticated user calls `PUT /api/v1/me/password`, which is routed to
   `CurrentUserRestApi.update_my_password` defined at `superset/views/users/api.py:305-235`.
   Before the view runs, `_enforce_password_change()` sees `request.endpoint` for this
   method, determines it is not exempt (per step 2), `password_change_required(user)` returns
   True, and the hook returns a redirect to the reset form or logout (lines 176-203) instead
   of `None`. As a result, the request never reaches `update_my_password()`, so forced-change
   users cannot use this API to change their password.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=983b7b1979854c91839c7dba4ccc2932&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=983b7b1979854c91839c7dba4ccc2932&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 305:316
   **Comment:**
   	*Api Mismatch: This new password-change route is not integrated with the forced-password-change enforcement allowlist, so users flagged with `password_must_change` will be redirected by the global before-request hook instead of reaching this API. That breaks the intended self-service flow exactly for first-login users who must change their password. Add this endpoint to the exempt password-change routes (or equivalent bypass logic) so flagged users can call it.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b6e978e9a3a0c7fef3e05e8ef9f9ff46c5c94e371873e10114291c0a61875f5e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b6e978e9a3a0c7fef3e05e8ef9f9ff46c5c94e371873e10114291c0a61875f5e&reaction=dislike'>👎</a>



##########
superset/views/users/api.py:
##########
@@ -163,6 +302,132 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.put_password",
+        log_to_statsd=False,
+    )
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies
+            ``AUTH_DB_CONFIG`` policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+
+        try:
+            body = _load_password_change_body(
+                self.current_user_password_put_schema,
+                request.json,
+            )
+            user_db = db.session.get(User, g.user.id)
+            if user_db is None:
+                return self.response_404()
+
+            old_hash = user_db.password
+            if not verify_auth_db_password(old_hash, body["current_password"]):
+                return self.response_400(message="Incorrect current password.")
+
+            new_hash = hash_auth_db_password(body["new_password"])
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            user_after = _commit_user_password_change(
+                self,
+                g.user.id,
+                old_hash,
+                new_hash,
+            )
+        except PasswordChangeConflictError:
+            return self.response_400(
+                message=(
+                    "Unable to update password. Your password may have been "
+                    "changed elsewhere; please try again."
+                ),
+            )
+        except SQLAlchemyError:
+            db.session.rollback()  # pylint: disable=consider-using-transaction
+            logger.exception("Failed to commit password change")
+            return self.response_500(
+                message="Unable to update password. Please try again.",
+            )
+
+        _reestablish_login_session(user_after)
+        return self.response(200, result=user_response_schema.dump(user_after))

Review Comment:
   **Suggestion:** The successful self-service password change path does not clear the `password_must_change` flag, unlike the existing self-service reset flow. If that flag is set, users can remain stuck in forced-change enforcement even after changing their password. Clear the forced-change attribute after a successful commit in this endpoint to keep behavior consistent with other self-service password-change paths. [incomplete implementation]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Users may remain stuck in forced-change enforcement.
   - ⚠️ Self-service password-change behavior diverges from reset-password flow.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Existing self-service password reset flows call
   `SupersetSecurityManager.reset_password()` in `superset/security/manager.py:945-35`, which
   after `super().reset_password(userid, password)` computes `is_self_service` (lines 22-27)
   and, when true, invokes `clear_password_must_change(int(userid))` from
   `superset/security/password_change.py:126-130`, setting
   `UserAttribute.password_must_change = False` (column defined in
   `superset/models/user_attributes.py:60-64`).
   
   2. The new API `CurrentUserRestApi.update_my_password` is implemented in
   `superset/views/users/api.py:146-235`. After validating the current and new passwords
   (lines 196-210), it hashes the new value and calls `_commit_user_password_change(...)` at
   lines 213-219 to update `User.password` and session stamps, but does not call
   `reset_password()` or `clear_password_must_change()`.
   
   3. When an authenticated user who currently has `password_must_change = True` (set via
   `set_password_must_change()` in `superset/security/password_change.py:95-123`)
   successfully calls `PUT /api/v1/me/password`, the code block at
   `superset/views/users/api.py:373-378` updates their password, then
   `_reestablish_login_session(user_after)` at line 393 refreshes their session and the API
   returns 200 at line 394. At no point in this path is `clear_password_must_change()`
   invoked, so the flag in `UserAttribute` remains True.
   
   4. Later, when `ENABLE_FORCE_PASSWORD_CHANGE` is (or becomes) enabled, the before-request
   hook `_enforce_password_change()` in `superset/security/password_change.py:155-175` calls
   `password_change_required(user)` (lines 86-92), which still reads
   `attr.password_must_change` as True. The user is therefore treated as still needing a
   forced password change and is redirected on non-exempt endpoints, even though they already
   updated their password via the self-service API, leaving account state inconsistent
   compared to the established `reset_password` self-service behavior.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=c8613f80d37b4db5b243a967e65e8374&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=c8613f80d37b4db5b243a967e65e8374&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 373:394
   **Comment:**
   	*Incomplete Implementation: The successful self-service password change path does not clear the `password_must_change` flag, unlike the existing self-service reset flow. If that flag is set, users can remain stuck in forced-change enforcement even after changing their password. Clear the forced-change attribute after a successful commit in this endpoint to keep behavior consistent with other self-service password-change paths.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ddb8da83131de34c58fe83cea3b6ee989657ad1268f92c963bd5347a503a95fa&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ddb8da83131de34c58fe83cea3b6ee989657ad1268f92c963bd5347a503a95fa&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4780974822

   Really nice scope here, and thanks for the docs + tests — this is a meaty one.
   
   CI is red across the board though (pre-commit, lint-frontend, unit-tests, and every DB integration variant), so we can't merge as-is. A couple of those look like real bugs rather than flakes:
   
   In `auth_user_db` you read `current_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"]` as a subscript, but I don't see that key defined anywhere in `config.py` (only `AUTH_DB_CONFIG` got added). That'll `KeyError` on every failed-login path — was it meant to live under `AUTH_DB_CONFIG`, or get its own default?
   
   And in `tests/integration_tests/users/api_tests.py` `test_put_my_password_invalidates_cloned_session_client` calls a bare `login(client_a, ADMIN_USERNAME)` that's never imported. `self.login(...)` is the only one in scope. That'd `NameError` the test, which probably explains part of the red.
   
   There's also a healthy stack of unresolved bot threads. If you don't mind giving those a pass to assess/address/resolve (the AUTH_TYPE gating on the admin Users-list reset action and the frontend/backend digit-rule mismatch in `generateAuthDbPassword.ts` looked worth a real look). Once CI's green we'll re-review. Holler if you want a hand. Thanks!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106963189


##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -48,19 +50,35 @@ function UserInfoModal({
     : {};
   const handleFormSubmit = async (values: FormValues) => {
     try {
-      const { confirm_password, ...payload } = values;
-      await SupersetClient.put({
-        endpoint: `/api/v1/me/`,
-        jsonPayload: { ...payload },
-      });
-      addSuccessToast(
-        isEditMode
-          ? t('The user was updated successfully')
-          : t('The password reset was successful'),
-      );
+      if (isEditMode) {
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/`,
+          jsonPayload: values,
+        });
+        addSuccessToast(t('The user was updated successfully'));
+      } else {
+        const { confirm_password, ...passwordPayload } = values;
+        void confirm_password;
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/password`,
+          jsonPayload: {
+            current_password: String(passwordPayload.current_password),
+            new_password: String(passwordPayload.new_password),
+          },

Review Comment:
   **🟠 Architect Review — HIGH**
   
   The self-service password reset modal always calls PUT /api/v1/me/password, but the backend explicitly rejects this endpoint when AUTH_TYPE is not AUTH_DB. In OAuth/LDAP/REMOTE_USER deployments, users still see the "Reset my password" UI and will hit a 400 error every time.
   
   **Suggestion:** In the frontend, gate the reset-password action on the configured AUTH_TYPE (available via bootstrap frontend_config.AUTH_TYPE), hiding or disabling this flow when AUTH_TYPE is not AUTH_DB, or instead routing users to external identity-management guidance in those modes.
   
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2063%3A67%0A%2A%2AComment%3A%2A%2A%0A%09%2AHIGH%3A%20The%20self-service%20password%20reset%20modal%20always%20calls%20PUT%20%2Fapi%2Fv1%2Fme%2Fpassword%2C%20but%20the%20backend%20explicitly%20rejects%20this%20endpoint%20when%20AUTH_TYPE%20is%20not%20AUTH_DB.%20In%20OAuth%2FLDAP%2FREMOTE_USER%20deployments%2C%20users%20still%20see%20the%20%22Reset%20my%20password%22%20UI%20and%20will%20hit%20a%20400%20error%20every%20time.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20le
 ft%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2063%3A67%0A%2A%2AComment%3A%2A%2A%0A%09%2AHIGH%3A%20The%20self-service%20password%20reset%20modal%20always%20calls%20PUT%20%2Fapi%2Fv1%2Fme%2Fpassword%2C%20but%20the%20backend%20explicitly%20rejects%20this%20endpoint%20when%20AUTH_TYPE%20is%20not%20AUTH_DB.%20In%20OAuth%2FLDAP%2FREMOTE_USER%20deployments%2C%20users%20still%20see%20the%20%22Reset%20my%20password%22%20UI%20and%20will%20hit%20a%20400%20error%20every%20time.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 63:67
   **Comment:**
   	*HIGH: The self-service password reset modal always calls PUT /api/v1/me/password, but the backend explicitly rejects this endpoint when AUTH_TYPE is not AUTH_DB. In OAuth/LDAP/REMOTE_USER deployments, users still see the "Reset my password" UI and will hit a 400 error every time.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3506593918


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,380 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    from flask import current_app
+
+    revalidation_seconds = int(
+        current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    )
+    if revalidation_seconds > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register login and request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    from flask_login import user_logged_in
+
+    if not getattr(app, "superset_session_auth_stamp_login_connected", False):
+        app.superset_session_auth_stamp_login_connected = True
+
+        @user_logged_in.connect
+        def _sync_stamp_after_login(sender: Flask, user: Any, **extra: Any) -> None:  # noqa: ARG001
+            """Copy the DB stamp into the session after Flask-Login finalizes it."""
+            sync_session_auth_stamp_on_login(user)
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> None:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            _cache_user_session_stamp(user_id, new_stamp)
+            return
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    _cache_user_session_stamp(user_id, new_stamp)
+
+
+def _resolve_stamp_check_user_id() -> int | None:
+    """Return the integer user id for the stamp check, or None to skip it."""
+    from flask import current_app
+    from flask_appbuilder.const import AUTH_DB
+    from sqlalchemy.orm.exc import DetachedInstanceError
+
+    if not has_request_context():
+        return None
+    # The stamp is only ever bumped on AUTH_DB password changes; skip the DB
+    # hit for other auth backends where the stamp never changes after login.
+    if current_app.config.get("AUTH_TYPE") != AUTH_DB:
+        return None
+    if not getattr(current_user, "is_authenticated", False):
+        return None
+    if getattr(current_user, "is_guest_user", False):
+        return None
+    try:
+        raw_id = current_user.get_id()
+    except DetachedInstanceError:
+        # The user object was detached from the session (e.g. after a rollback
+        # in a previous before_request hook). Fail open — Flask-Login will
+        # reload the user on the next request.
+        logger.warning(
+            "Skipping session auth stamp check: current_user is detached",
+            exc_info=True,
+        )
+        return None
+    if raw_id is None:
+        return None
+    try:
+        return int(raw_id)
+    except (TypeError, ValueError):
+        return None
+
+
+def _should_skip_stamp_db_lookup(
+    user_id: int, method: str, sess_stamp: str | None
+) -> bool:
+    """Return True when a recent DB check lets us skip another on read-only traffic."""
+    from flask import current_app
+
+    if method not in _SAFE_METHODS or sess_stamp is None:
+        return False
+
+    revalidation_seconds = int(
+        current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    )
+    if revalidation_seconds <= 0:
+        return False
+
+    validated_at = session.get(SESSION_AUTH_STAMP_VALIDATED_AT_KEY)
+    if validated_at is None:
+        return False
+    try:
+        elapsed = time.time() - float(validated_at)
+    except (TypeError, ValueError):
+        return False
+    if elapsed >= revalidation_seconds:
+        return False
+
+    # Only skip when this session still carries the stamp we last validated and
+    # the shared cache agrees (so password rotation on another client is visible).
+    if session.get(SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY) != sess_stamp:
+        return False
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    return cached_stamp == sess_stamp
+
+
+def _mark_session_stamp_validated(db_stamp: str | None = None) -> None:
+    session[SESSION_AUTH_STAMP_VALIDATED_AT_KEY] = time.time()
+    if db_stamp is not None:
+        session[SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY] = db_stamp
+    session.modified = True
+
+
+def _load_db_stamp_for_user(
+    user_id: int, *, use_cache: bool = True
+) -> tuple[str | None, bool]:
+    """
+    Return ``(stamp, db_error)``.
+
+    ``db_error`` is True when the database lookup failed and the check should
+    fail open. ``stamp`` is None when no row exists.
+    """
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    if use_cache and cached_stamp is not None:
+        return cached_stamp, False
+
+    try:
+        with db.session.begin_nested():
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            db_stamp = row.stamp if row is not None else None
+    except SQLAlchemyError:
+        logger.warning(
+            "Skipping session auth stamp check due to a database error",
+            exc_info=True,
+        )
+        return None, True
+
+    if db_stamp is not None:
+        _cache_user_session_stamp(user_id, db_stamp)
+    return db_stamp, False
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from flask import request as flask_request
+
+    user_id = _resolve_stamp_check_user_id()
+    if user_id is None:
+        logger.debug(
+            "Stamp validation skipped: no user_id for %s %s",
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)
+    if _should_skip_stamp_db_lookup(user_id, flask_request.method, sess_stamp):
+        logger.debug(
+            "Session auth stamp DB check skipped (recently validated) for user_id=%s "
+            "on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    db_stamp, db_error = _load_db_stamp_for_user(
+        user_id,
+        use_cache=flask_request.method in _SAFE_METHODS,
+    )
+    if db_error:
+        return
+
+    if db_stamp is None:
+        # No stamp row means the session predates stamp tracking. Allow the
+        # request without a check — sync_session_auth_stamp_on_login creates
+        # the row on every login, so this only occurs for sessions that
+        # pre-date the feature or whose row was removed independently.
+        # Committing inside a before_request hook would expire SQLAlchemy
+        # objects loaded by earlier hooks and can cause DetachedInstanceError
+        # in route handlers, so we never create the row here.
+        logger.debug(
+            "Session auth stamp check skipped: no DB stamp row for user_id=%s on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    # A missing stamp means the session predates stamp tracking (or was never
+    # stamped). Once a DB stamp row exists, adopting it silently would let such
+    # a session survive a password change, so treat a missing stamp as invalid.
+    logger.debug(
+        "Validating session stamp for user_id=%s on %s %s: sess_stamp=%s, db_stamp=%s",
+        user_id,
+        flask_request.method,
+        flask_request.path,
+        sess_stamp,
+        db_stamp,
+    )
+    if sess_stamp is None or sess_stamp != db_stamp:
+        # Sessions authenticated before stamp tracking, or a login that did not
+        # persist the stamp into the signed cookie, carry no sess stamp. Adopt
+        # the current DB value so the request can proceed; password rotation
+        # still invalidates sessions that carry an outdated non-null stamp.
+        if sess_stamp is None and db_stamp is not None:
+            session[SESSION_AUTH_STAMP_SESSION_KEY] = db_stamp
+            _mark_session_stamp_validated(db_stamp)
+            _cache_user_session_stamp(user_id, db_stamp)
+            return
+        logger.info(
+            "Session stamp mismatch for user_id=%s on %s %s: invalidating session",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        logout_user()
+        session.clear()
+        # Immediately return 401 Unauthorized to reject the request with
+        # an invalid session, rather than relying on the @protect() decorator
+        from werkzeug.exceptions import Unauthorized
+
+        raise Unauthorized("Session invalidated")
+
+    _mark_session_stamp_validated(db_stamp)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Unreachable code after raise</b></div>
   <div id="fix">
   
   Line 380 is unreachable dead code placed after `raise Unauthorized(...)` on line 378. A `raise` statement unconditionally transfers control and prevents any subsequent code from running. Remove this line to eliminate maintenance confusion.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #9248cb</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3427049907


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,259 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    crypto.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(
+  policy: AuthDbPasswordPolicy,
+): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+function satisfiesAuthDbPasswordPolicy(
+  password: string,
+  policy: AuthDbPasswordPolicy,
+): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  return Object.values(checks).every(Boolean);
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(password: string): boolean {
+  return satisfiesAuthDbPasswordPolicy(password, AUTH_DB_DEFAULT_PASSWORD_POLICY);
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = Math.max(1, Number(policy.password_min_length) || 1);
+  return {
+    minLength: password.length >= minLength,

Review Comment:
   **Suggestion:** Using JavaScript `password.length` for minimum-length validation is not backend-equivalent for non-BMP Unicode characters (e.g., emoji), because JS counts UTF-16 code units while Python counts Unicode code points. This can produce client-side "valid" passwords that are rejected by backend policy. Count Unicode code points instead to match server behavior. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Users see client "valid" but server rejects Unicode passwords.
   - ⚠️ Affects `/api/v1/me/password` self-service password changes.
   - ⚠️ Affects admin resets using UserListResetPasswordModal validation.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In the self-service password change modal, `UserInfoModal`
   (superset-frontend/src/features/userInfo/UserInfoModal.tsx:82-145), the `new_password`
   `FormItem` uses `getPasswordPolicyError` to validate the candidate password before
   submission.
   
   2. `getPasswordPolicyError` in `UserInfoModal` (lines 15-17) calls the shared helper
   `getAuthDbPasswordPolicyError(password, passwordPolicy)`, which in turn calls
   `getAuthDbPasswordPolicyChecks(password, policy)` from
   superset-frontend/src/utils/generateAuthDbPassword.ts:182-199.
   
   3. `getAuthDbPasswordPolicyChecks` computes `minLength` as `password.length >= minLength`
   (generateAuthDbPassword.ts:186-188), where `password.length` is the JavaScript string
   length in UTF-16 code units; for a password consisting of 6 non-BMP emoji characters,
   `password.length` is 12 even though Python sees length 6.
   
   4. On the backend, `validate_auth_db_password`
   (superset/superset/utils/auth_db_password.py:164-177) uses `len(password) < min_len` with
   Python's Unicode code point length, so the same 6-emoji password with
   `password_min_length` 12 passes the frontend check (length 12) but fails the backend check
   (length 6), causing the client to accept the password in the modal while the server
   responds with a 400 error when `PUT /api/v1/me/password` or the admin reset endpoint is
   called.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9b8c0c394b84499dae64d51bc8202132&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9b8c0c394b84499dae64d51bc8202132&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 188:188
   **Comment:**
   	*Api Mismatch: Using JavaScript `password.length` for minimum-length validation is not backend-equivalent for non-BMP Unicode characters (e.g., emoji), because JS counts UTF-16 code units while Python counts Unicode code points. This can produce client-side "valid" passwords that are rejected by backend policy. Count Unicode code points instead to match server behavior.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c61c3316bb3e400ac7b30870f7ddf6b55b3bbc337243f492caa2bb59eeca4b65&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c61c3316bb3e400ac7b30870f7ddf6b55b3bbc337243f492caa2bb59eeca4b65&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/users/utils.ts:
##########
@@ -19,8 +19,40 @@
 import { t } from '@apache-superset/core/translation';
 import { SupersetClient } from '@superset-ui/core';
 import { SelectOption } from 'src/components/ListView';
+import type { UserObject } from 'src/pages/UsersList/types';
 import { FormValues } from './types';
 
+/** Fields required for PUT `/api/v1/security/users/:id` when changing password only. */
+export function buildSecurityUserUpdatePayload(user: UserObject): FormValues {
+  return {
+    first_name: user.first_name,
+    last_name: user.last_name,
+    username: user.username,
+    email: user.email,
+    active: user.active,
+    roles: (user.roles ?? []).map(r => r.id),
+    groups: (user.groups ?? []).map(g => g.id),
+  };
+}
+
+export const fetchSecurityUser = async (userId: number): Promise<UserObject> => {
+  const { json } = await SupersetClient.get({
+    endpoint: `/api/v1/security/users/${userId}`,
+  });
+  return json.result as UserObject;
+};
+
+export const resetUserPassword = async (
+  userId: number,
+  password: string,
+): Promise<void> => {
+  const freshUser = await fetchSecurityUser(userId);
+  await updateUser(userId, {
+    ...buildSecurityUserUpdatePayload(freshUser),
+    password,
+  });

Review Comment:
   **Suggestion:** This reset flow does a read-then-write of the full user object, which creates a lost-update race: if another admin edits the same user between `fetchSecurityUser` and `updateUser`, the second call can overwrite those concurrent changes with stale values. Use a password-only API/update path (or optimistic concurrency/version checks) so password reset does not rewrite unrelated profile/role fields. [race condition]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Admin password reset can silently revert recent profile edits.
   - ❌ Concurrent role/group changes can be lost without warning.
   - ⚠️ Affects admin UI flows using UserListResetPasswordModal.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. An admin opens the reset-password modal for a user in the Users list; the modal
   component `UserListResetPasswordModal`
   (superset-frontend/src/features/users/UserListResetPasswordModal.tsx:49-54) is rendered
   and receives the target `user` prop.
   
   2. When the admin submits the form, `handleFormSubmit` in `UserListResetPasswordModal`
   (superset-frontend/src/features/users/UserListResetPasswordModal.tsx:80-107) calls
   `resetUserPassword(user.id, rest.password)` from `./utils`.
   
   3. `resetUserPassword` (superset-frontend/src/features/users/utils.ts:45-54) performs a
   read-then-write sequence: it first calls `fetchSecurityUser(userId)` (GET
   `/api/v1/security/users/:id`, utils.ts:38-43) to retrieve the current full user object,
   then calls `updateUser(userId, { ...buildSecurityUserUpdatePayload(freshUser), password
   })` which issues a PUT to `/api/v1/security/users/:id` with all profile/role fields plus
   the new password.
   
   4. In parallel, a second admin edits the same user via the edit-user modal `UserListModal`
   (superset-frontend/src/features/users/UserListModal.tsx, `handleFormSubmit` uses `await
   updateUser(user.id, values)` at lines ~80-90). If the second admin saves their changes
   *after* `resetUserPassword` has fetched the user but *before* `resetUserPassword` issues
   its PUT, the subsequent PUT from `resetUserPassword` writes the stale snapshot from
   `freshUser` (via `buildSecurityUserUpdatePayload` at utils.ts:26-35), overwriting the
   other admin's recent changes to names, email, roles, or groups along with the new
   password.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e41967c6ac84474bb9d6a7151e3c8515&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e41967c6ac84474bb9d6a7151e3c8515&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/utils.ts
   **Line:** 49:53
   **Comment:**
   	*Race Condition: This reset flow does a read-then-write of the full user object, which creates a lost-update race: if another admin edits the same user between `fetchSecurityUser` and `updateUser`, the second call can overwrite those concurrent changes with stale values. Use a password-only API/update path (or optimistic concurrency/version checks) so password reset does not rewrite unrelated profile/role fields.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=94bf52972297191d49a12177e9d78d4f1dd5482a9951dbc3f5c21803b4e8d519&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=94bf52972297191d49a12177e9d78d4f1dd5482a9951dbc3f5c21803b4e8d519&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4747326894

   The session-before-commit fix looks good, thanks! Just a couple things still holding up the PR. @dpgaspar asked about reusing the existing `Log` table rather than a new `auth_audit_log` (plus its migration). Also, this is still the XXL bundle you and @EnxDev agreed to narrow down to Pillar 1, with the audit-log infra and admin reset riding along. Can we land just the `/me/password` change first and split the rest out? This also needs a rebase... happy to try tackling that part if it helps.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3425688368


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,183 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  const buf = new Uint32Array(1);
+  crypto.getRandomValues(buf);
+  return buf[0]! % maxExclusive;

Review Comment:
   **Suggestion:** Using modulo reduction on random 32-bit values introduces statistical bias, so some characters are selected more often than others. Because this function is used for password generation, this weakens entropy and should use rejection sampling to produce an unbiased integer range. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Generated passwords have slightly biased character distribution.
   - ⚠️ Overall password entropy marginally lower than ideal uniform.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open either password-generation UI that uses AUTH_DB passwords: the self-service change
   password modal (`ChangePasswordModal` / `UserInfoModal` in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:77-79,108-138,200-8`) or the
   admin reset password modal (`UserListResetPasswordModal` in
   `superset-frontend/src/features/users/UserListResetPasswordModal.tsx:18-23,41-53`).
   
   2. In either modal, click the `GeneratePasswordInputSuffix` icon on the new-password
   field. This triggers `onGenerate={() => { const pwd = generateAuthDbPassword(); ... }}` at
   `UserInfoModal.tsx:200-8` or `UserListResetPasswordModal.tsx:45-50`, which calls
   `generateAuthDbPassword()` defined in
   `superset-frontend/src/utils/generateAuthDbPassword.ts:43-61`.
   
   3. `generateAuthDbPassword()` constructs the password by repeatedly calling `pick()` to
   select characters from the UPPER/LOWER/DIGIT/SPECIAL pools
   (`generateAuthDbPassword.ts:47-55,9-13,30-32`). `pick()` in turn calls
   `secureRandomInt(pool.length)` (`generateAuthDbPassword.ts:30-32,24-28`).
   
   4. `secureRandomInt()` obtains a 32-bit random integer from `crypto.getRandomValues` and
   returns `buf[0] % maxExclusive` (`generateAuthDbPassword.ts:24-27`). Because
   `maxExclusive` (e.g., the length of `ALPHANUM + SPECIAL`, currently 68) generally does not
   evenly divide `2^32`, some residues occur one time more than others in the modulo
   reduction, making certain characters slightly more probable. Every generated password goes
   through this biased selection path, so the distribution of characters is not perfectly
   uniform even though `crypto.getRandomValues` itself is unbiased.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ed3b9738c55241ea8110fafad509e0de&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ed3b9738c55241ea8110fafad509e0de&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 106:106
   **Comment:**
   	*Security: Using modulo reduction on random 32-bit values introduces statistical bias, so some characters are selected more often than others. Because this function is used for password generation, this weakens entropy and should use rejection sampling to produce an unbiased integer range.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8955ab16ca1b9bfce93ba4c40b83766d7737bfac9b28a8193eaf52220cf5f632&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8955ab16ca1b9bfce93ba4c40b83766d7737bfac9b28a8193eaf52220cf5f632&reaction=dislike'>👎</a>



##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,183 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  const buf = new Uint32Array(1);
+  crypto.getRandomValues(buf);
+  return buf[0]! % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(password: string): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(
+    password,
+    AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  );
+  return (
+    checks.minLength &&
+    checks.uppercase &&
+    checks.lowercase &&
+    checks.digit &&
+    checks.special &&
+    checks.commonPassword
+  );
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = Math.max(1, Number(policy.password_min_length) || 1);
+  return {
+    minLength: password.length >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\d/.test(password),
+    special:
+      !policy.password_require_special ||
+      /[^\p{L}\p{N}\s]/u.test(password),
+    commonPassword:
+      !policy.password_common_list_check ||
+      !AUTH_DB_COMMON_PASSWORDS.has(password.toLowerCase().trim()),
+  };
+}
+
+/**
+ * Returns a random password that should pass ``validate_auth_db_password`` with default
+ * ``AUTH_DB_CONFIG`` on the server.
+ */
+export function generateAuthDbPassword(): string {
+  const minLen = AUTH_DB_PASSWORD_MIN_LENGTH;
+  const maxAttempts = 64;
+  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
+    const chars: string[] = [
+      pick(UPPER),
+      pick(LOWER),
+      pick(DIGIT),
+      pick(SPECIAL),
+    ];
+    const pool = ALPHANUM + SPECIAL;
+    while (chars.length < minLen) {
+      chars.push(pick(pool));
+    }
+    shuffleInPlace(chars);
+    const password = chars.join('');
+    if (satisfiesDefaultAuthDbPasswordPolicy(password)) {
+      return password;
+    }
+  }
+  throw new Error('generateAuthDbPassword: exhausted retries');
+}

Review Comment:
   **Suggestion:** The password generator is hard-wired to the default policy (`AUTH_DB_PASSWORD_MIN_LENGTH` + `satisfiesDefaultAuthDbPasswordPolicy`) and cannot honor a customized `AUTH_DB_CONFIG`. In deployments with stricter policy (for example longer minimum length), generated passwords will frequently fail server-side validation and break the "generate password" flow. Make the generator accept a policy argument and validate against that policy instead of the defaults. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Self-service password generator fails with stricter AUTH_DB_CONFIG.
   - ⚠️ Admin reset generator produces passwords rejected by backend.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure a stricter AUTH_DB password policy in `superset_config.py`, for example
   `AUTH_DB_CONFIG = {"password_min_length": 16, ...}` as documented in
   `docs/admin_docs/configuration/configuring-superset.mdx:15-29`. On the backend this is
   enforced by `validate_auth_db_password()` in `superset/utils/auth_db_password.py:164-176`,
   which reads `password_min_length` from the merged config.
   
   2. Start Superset with `AUTH_TYPE = AUTH_DB`, log in as a regular user, and open the
   "Reset password" dialog, which is implemented by `ChangePasswordModal`/`UserInfoModal` in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx:77-79,46-52`. This modal
   fetches the effective policy from `/api/v1/me/password/policy` and stores it in
   `passwordPolicy` (`UserInfoModal.tsx:54-68`).
   
   3. In the "New password" field of `ResetPasswordFields` (`UserInfoModal.tsx:108-139`),
   click the generate icon suffix. The `onGenerate` handler at `UserInfoModal.tsx:200-8`
   calls `generateAuthDbPassword()` from
   `superset-frontend/src/utils/generateAuthDbPassword.ts:43-61`, which uses the hard-coded
   constant `AUTH_DB_PASSWORD_MIN_LENGTH` (12) instead of the fetched `passwordPolicy`.
   
   4. The generated password is exactly 12 characters long (loop at
   `generateAuthDbPassword.ts:44-55`) and is then validated client-side by
   `getPasswordPolicyError()` (`UserInfoModal.tsx:75-82`), which calls
   `getAuthDbPasswordPolicyChecks(password, passwordPolicy)`
   (`generateAuthDbPassword.ts:60-66`). With `passwordPolicy.password_min_length` = 16,
   `checks.minLength` is false, so the form validator rejects the generated password (showing
   "Password must be at least 16 characters long"), even though the generation logic believes
   it produced a policy-compliant password. The same generator is also used in the admin
   reset modal `UserListResetPasswordModal`
   (`superset-frontend/src/features/users/UserListResetPasswordModal.tsx:45-50`), where
   generated 12-character passwords will be accepted by the frontend but rejected by backend
   `validate_auth_db_password()`, causing API errors.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=a1e21183c9724f089d08c22c8e6c078f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=a1e21183c9724f089d08c22c8e6c078f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 162:183
   **Comment:**
   	*Api Mismatch: The password generator is hard-wired to the default policy (`AUTH_DB_PASSWORD_MIN_LENGTH` + `satisfiesDefaultAuthDbPasswordPolicy`) and cannot honor a customized `AUTH_DB_CONFIG`. In deployments with stricter policy (for example longer minimum length), generated passwords will frequently fail server-side validation and break the "generate password" flow. Make the generator accept a policy argument and validate against that policy instead of the defaults.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=821b27650230577bf30159564312ea3e3d5abcc49aac0da716f0bb156edf7568&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=821b27650230577bf30159564312ea3e3d5abcc49aac0da716f0bb156edf7568&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4514554635

   Apologies for the delay. I had some family emergency I will finish pillar 1 this week and finish the first PR. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5005905914

   Thanks for sticking with this one, @shantanukhond. 
   
   CI's failing now, as you probably noticed. That includes the ruff-format nit on `auth_session_stamp.py`. 
   
   `test_user_gets_all_charts` and `test_user_gets_all_datasets` are failing 401 instead of 200 on every DB backend, which looks like it's due to the session-stamp changes breaking logins more broadly than just the password flow.
   
   The login-timing side-channel between real and nonexistent users keeps getting flagged in reviews, and the lockout/rate-limit keys in `AUTH_DB_CONFIG` are documented but not wired into the login path. The session-stamp fail-open question I left open for @dpgaspar last pass is still unanswered too, if you can tend to that.
   
   Need CI green and those closed out before this is mergeable. Happy to jump in if I can help resolve bot comments or any of the above.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3608483987


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,390 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def _invalidate_stale_auth_session() -> None:
+    """Log out, clear remember-me, and raise 401 for a stamp mismatch."""
+    from werkzeug.exceptions import Unauthorized
+
+    logout_user()
+    session.clear()
+    # ``session.clear()`` drops the remember marker; set it after so Flask-Login
+    # still deletes any remember-me cookie on the response.
+    session["_remember"] = "clear"
+    raise Unauthorized("Session invalidated")
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> str:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return new_stamp
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    return new_stamp

Review Comment:
   **Suggestion:** The stamp-rotation function updates only the database row and returns without updating the shared cache, so other sessions can keep validating against a stale cached stamp until cache expiry. This delays cross-session invalidation after a password change; update or invalidate the cached value as part of the same rotation path. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Old browser sessions remain valid after password change.
   - ⚠️ Cross-session logout semantics not reliably enforced.
   - ⚠️ Security depends on cache eviction, not DB stamp.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. A user logs in, triggering `sync_session_auth_stamp_on_login` in
   `superset/utils/auth_session_stamp.py:165-177`, which calls
   `ensure_user_session_stamp_value` at lines 115-137 and then `_cache_user_session_stamp` at
   lines 85-93 to store the current stamp for `user_id` in the shared cache.
   
   2. The instance is configured with `SESSION_AUTH_STAMP_REVALIDATION_SECONDS > 0` (read in
   `_get_revalidation_seconds` at lines 50-61), so `_stamp_cache_timeout_seconds` at lines
   64-67 and `_should_skip_stamp_db_lookup` at lines 244-271 rely on the cached stamp to
   avoid frequent DB lookups on safe methods (`GET`, `HEAD`, `OPTIONS`).
   
   3. The user changes their password via the new AUTH_DB password change flow, which rotates
   the per-user session stamp by calling `bump_user_session_auth_stamp(user_id)` defined at
   lines 185-207; this function updates `row.stamp = new_stamp` at line 205 and returns at
   line 206 without calling `_cache_user_session_stamp`, leaving the cache entry set to the
   old stamp.
   
   4. Another active browser session for the same user sends a `GET` request;
   `validate_session_auth_stamp_for_request` at lines 312-390 runs on `before_request`,
   obtains `user_id` via `_resolve_stamp_check_user_id` at lines 209-242, and then calls
   `_should_skip_stamp_db_lookup` at lines 244-271. Because
   `SESSION_AUTH_STAMP_REVALIDATION_SECONDS > 0`, the method is safe, and the session carries
   the old stamp, `_get_cached_user_session_stamp` at lines 70-82 returns the stale value;
   `_should_skip_stamp_db_lookup` sees `cached_stamp == sess_stamp` and returns `True`, so
   the DB is not consulted and the outdated session remains valid even though the
   authoritative DB stamp has been rotated.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=6cf94779dfa348e88f4faeea59716112&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=6cf94779dfa348e88f4faeea59716112&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 205:206
   **Comment:**
   	*Security: The stamp-rotation function updates only the database row and returns without updating the shared cache, so other sessions can keep validating against a stale cached stamp until cache expiry. This delays cross-session invalidation after a password change; update or invalidate the cached value as part of the same rotation path.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6c24ca5def8d063a0da1d9c3e398eb0b01bd570a22796ecb6a744cd67e3700b3&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6c24ca5def8d063a0da1d9c3e398eb0b01bd570a22796ecb6a744cd67e3700b3&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3602473973


##########
superset/views/users/api.py:
##########
@@ -161,6 +309,135 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies
+            ``AUTH_DB_CONFIG`` policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            429:
+              $ref: '#/components/responses/400'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+
+        try:
+            body = _load_password_change_body(
+                self.current_user_password_put_schema,
+                request.json,
+            )
+            user_db = db.session.get(User, g.user.id)
+            if user_db is None:
+                return self.response_404()
+
+            old_hash = user_db.password
+            if not verify_auth_db_password(old_hash, body["current_password"]):
+                return self.response_400(message="Incorrect current password.")
+
+            new_hash = hash_auth_db_password(body["new_password"])
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            user_after, new_stamp = _commit_user_password_change(
+                self,
+                g.user.id,
+                old_hash,
+                new_hash,
+            )
+        except PasswordChangeConflictError:
+            return self.response_400(
+                message=(
+                    "Unable to update password. Your password may have been "
+                    "changed elsewhere; please try again."
+                ),
+            )
+        except SQLAlchemyError:
+            db.session.rollback()  # pylint: disable=consider-using-transaction
+            logger.exception("Failed to commit password change")
+            return self.response_500(
+                message="Unable to update password. Please try again.",
+            )
+
+        cache_user_session_auth_stamp(g.user.id, new_stamp)
+        _reestablish_login_session(user_after)
+        _log_audit_event("UserPasswordChanged", {"user_id": g.user.id})

Review Comment:
   Adding initiated_by and source_ip. Anything else you can suggest?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4904308816

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4297922452

   ## [Codecov](https://app.codecov.io/gh/apache/superset/pull/39469?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :white_check_mark: All modified and coverable lines are covered by tests.
   :white_check_mark: Project coverage is 67.01%. Comparing base ([`e1ed500`](https://app.codecov.io/gh/apache/superset/commit/e1ed5003a89683582cff22b33664fa9b2c30d3b2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`8746e5b`](https://app.codecov.io/gh/apache/superset/commit/8746e5bfad4c4391c598682c71f0c629661ff32c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   :warning: Report is 9 commits behind head on master.
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   ```diff
   @@            Coverage Diff             @@
   ##           master   #39469      +/-   ##
   ==========================================
   + Coverage   64.57%   67.01%   +2.44%     
   ==========================================
     Files        2561     1844     -717     
     Lines      133470    75346   -58124     
     Branches    31015    24009    -7006     
   ==========================================
   - Hits        86182    50493   -35689     
   + Misses      45796    24853   -20943     
   + Partials     1492        0    -1492     
   ```
   
   | [Flag](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
   |---|---|---|
   | [hive](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [mysql](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [postgres](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [presto](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [python](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `100.00% <ø> (+37.90%)` | :arrow_up: |
   | [sqlite](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [unit](https://app.codecov.io/gh/apache/superset/pull/39469/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `100.00% <ø> (ø)` | |
   
   Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
   </details>
   
   [:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/apache/superset/pull/39469?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).   
   :loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3655700976


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,392 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY: str = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY: str = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY: str = (
+    "_auth_session_stamp_validated_db_stamp"
+)
+
+_SAFE_METHODS: frozenset[str] = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX: str = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS: int = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def _invalidate_stale_auth_session() -> None:
+    """Log out, clear remember-me, and raise 401 for a stamp mismatch."""
+    from werkzeug.exceptions import Unauthorized
+
+    logout_user()
+    session.clear()
+    # ``session.clear()`` drops the remember marker; set it after so Flask-Login
+    # still deletes any remember-me cookie on the response.
+    session["_remember"] = "clear"
+    raise Unauthorized("Session invalidated")
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> str:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return new_stamp
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    return new_stamp
+
+
+def _resolve_stamp_check_user_id() -> int | None:
+    """Return the integer user id for the stamp check, or None to skip it."""
+    from flask import current_app
+    from flask_appbuilder.const import AUTH_DB
+    from sqlalchemy.orm.exc import DetachedInstanceError
+
+    if not has_request_context():
+        return None
+    # The stamp is only ever bumped on AUTH_DB password changes; skip the DB
+    # hit for other auth backends where the stamp never changes after login.
+    if current_app.config.get("AUTH_TYPE") != AUTH_DB:
+        return None
+    if not getattr(current_user, "is_authenticated", False):
+        return None
+    if getattr(current_user, "is_guest_user", False):
+        return None
+    try:
+        raw_id = current_user.get_id()
+    except DetachedInstanceError:
+        # The user object was detached from the session (e.g. after a rollback
+        # in a previous before_request hook). Fail open — Flask-Login will
+        # reload the user on the next request.
+        logger.warning(
+            "Skipping session auth stamp check: current_user is detached",
+            exc_info=True,
+        )
+        return None
+    if raw_id is None:
+        return None
+    try:
+        return int(raw_id)
+    except (TypeError, ValueError):
+        return None
+
+
+def _should_skip_stamp_db_lookup(
+    user_id: int, method: str, sess_stamp: str | None
+) -> bool:
+    """Return True when a recent DB check lets us skip another on read-only traffic."""
+    if method not in _SAFE_METHODS or sess_stamp is None:
+        return False
+
+    revalidation_seconds = _get_revalidation_seconds()
+    if revalidation_seconds <= 0:
+        return False
+
+    validated_at = session.get(SESSION_AUTH_STAMP_VALIDATED_AT_KEY)
+    if validated_at is None:
+        return False
+    try:
+        elapsed = time.time() - float(validated_at)
+    except (TypeError, ValueError):
+        return False
+    if elapsed >= revalidation_seconds:
+        return False
+
+    # Only skip when this session still carries the stamp we last validated and
+    # the shared cache agrees (so password rotation on another client is visible).
+    if session.get(SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY) != sess_stamp:
+        return False
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    return cached_stamp == sess_stamp
+
+
+def _mark_session_stamp_validated(db_stamp: str | None = None) -> None:
+    session[SESSION_AUTH_STAMP_VALIDATED_AT_KEY] = time.time()
+    if db_stamp is not None:
+        session[SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY] = db_stamp
+    session.modified = True
+
+
+def _load_db_stamp_for_user(
+    user_id: int, *, use_cache: bool = True
+) -> tuple[str | None, bool]:
+    """
+    Return ``(stamp, db_error)``.
+
+    ``db_error`` is True when the database lookup failed and the check should
+    fail open. ``stamp`` is None when no row exists.
+    """
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if use_cache:
+        cached_stamp = _get_cached_user_session_stamp(user_id)
+        if cached_stamp is not None:
+            return cached_stamp, False
+
+    try:
+        with db.session.begin_nested():
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            db_stamp = row.stamp if row is not None else None
+    except SQLAlchemyError:
+        logger.warning(
+            "Skipping session auth stamp check due to a database error",
+            exc_info=True,
+        )
+        return None, True
+
+    if db_stamp is not None:
+        _cache_user_session_stamp(user_id, db_stamp)

Review Comment:
   **Suggestion:** This cache write can reinsert an obsolete stamp after a password rotation. A request that read the old database stamp before the rotation may finish afterward and overwrite the shared cache with that old value; read-only requests can then trust the stale cache and skip the database check, allowing old sessions to remain valid for the revalidation interval. [race condition]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Revoked browser sessions can remain valid temporarily.
   - ⚠️ Safe requests may skip authoritative stamp validation.
   - ⚠️ Password-rotation session invalidation depends on cache ordering.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure `SESSION_AUTH_STAMP_REVALIDATION_SECONDS` to a positive value and
   authenticate an AUTH_DB user; `validate_session_auth_stamp_for_request()` at
   `superset/utils/auth_session_stamp.py:314` enables cache-based skipping for safe requests.
   
   2. Start a safe request using an old session stamp and pause it after
   `_load_db_stamp_for_user()` reads the old database stamp at
   `superset/utils/auth_session_stamp.py:299-301`, but before `_cache_user_session_stamp()`
   executes at lines 309-310.
   
   3. Change the user's password through `CurrentUserRestApi.update_my_password()` at
   `superset/views/users/api.py:312-408`; `_commit_user_password_change()` rotates the
   database stamp at `superset/views/users/api.py:149`, and the new stamp is written to the
   cache at line 398.
   
   4. Allow the paused request to continue. Lines 309-310 can overwrite the cache with the
   old stamp, after which `_should_skip_stamp_db_lookup()` at
   `superset/utils/auth_session_stamp.py:267-272` can trust that stale value for the
   revalidation interval, allowing the old session to pass safe-request validation.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=af43d5947a1247b1a9c721ad81659246&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=af43d5947a1247b1a9c721ad81659246&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 309:310
   **Comment:**
   	*Race Condition: This cache write can reinsert an obsolete stamp after a password rotation. A request that read the old database stamp before the rotation may finish afterward and overwrite the shared cache with that old value; read-only requests can then trust the stale cache and skip the database check, allowing old sessions to remain valid for the revalidation interval.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f4ef66961eb9c8c3de84ac010194d91c289447e3330b41bc48a2d556b69d693e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f4ef66961eb9c8c3de84ac010194d91c289447e3330b41bc48a2d556b69d693e&reaction=dislike'>👎</a>



##########
superset/views/users/api.py:
##########
@@ -161,6 +309,142 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies
+            ``AUTH_DB_CONFIG`` policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            429:
+              $ref: '#/components/responses/400'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+
+        try:
+            body = _load_password_change_body(
+                self.current_user_password_put_schema,
+                request.json,
+            )
+            user_db = db.session.get(User, g.user.id)
+            if user_db is None:
+                return self.response_404()
+
+            old_hash = user_db.password
+            if not verify_auth_db_password(old_hash, body["current_password"]):
+                return self.response_400(message="Incorrect current password.")
+
+            new_hash = hash_auth_db_password(body["new_password"])

Review Comment:
   **Suggestion:** The configured hash algorithm is not validated or converted into an API error before hashing. For an unsupported `password_hash_algorithm`, `hash_auth_db_password` can raise a `ValueError` or similar exception, but this handler only catches `ValidationError`, causing the endpoint to return an unintended 500 instead of the documented 400 response. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password changes fail under invalid hash configuration.
   - ⚠️ Clients receive an unintended server error.
   - ⚠️ Operators lack a clear configuration-validation response.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run Superset with `AUTH_TYPE` set to `AUTH_DB` and configure `AUTH_DB_CONFIG` with an
   unsupported password hash algorithm; the password-change endpoint checks this mode at
   `superset/views/users/api.py:353-358`.
   
   2. Authenticate with an AUTH_DB user and send JSON containing `current_password`,
   `new_password`, and `confirm_password` to `PUT /api/v1/me/password`, implemented by
   `CurrentUserRestApi.update_my_password()` at `superset/views/users/api.py:312-319`.
   
   3. The request passes `_load_password_change_body()` at
   `superset/views/users/api.py:107-114`, loads the user at lines 365-367, and verifies the
   current password at lines 369-371.
   
   4. Line 373 calls `hash_auth_db_password()` from
   `superset/utils/auth_db_password_hash.py`. If that utility raises `ValueError` or another
   configuration error for the unsupported algorithm, the surrounding handler only catches
   `ValidationError` at lines 374-375, so the request reaches the generic server-error
   handling instead of returning the documented client-facing 400 response.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=de155600afcc4fc19dfd4f1fa1eea213&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=de155600afcc4fc19dfd4f1fa1eea213&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 369:373
   **Comment:**
   	*Possible Bug: The configured hash algorithm is not validated or converted into an API error before hashing. For an unsupported `password_hash_algorithm`, `hash_auth_db_password` can raise a `ValueError` or similar exception, but this handler only catches `ValidationError`, causing the endpoint to return an unintended 500 instead of the documented 400 response.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f31ef5d43d698acf361d2cf4e6c5e568db928e755df89a2776b5ade6ccc7b3c0&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f31ef5d43d698acf361d2cf4e6c5e568db928e755df89a2776b5ade6ccc7b3c0&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3502729900


##########
superset/security/password_change.py:
##########
@@ -62,7 +62,18 @@
 )
 
 # Exact endpoint names (function views / Flask built-ins) that are always exempt.
-_EXEMPT_ENDPOINTS = frozenset({"static", "appbuilder.static", "health", "healthcheck"})
+_EXEMPT_ENDPOINTS = frozenset(
+    {
+        "static",
+        "appbuilder.static",
+        "health",
+        "healthcheck",
+        # Self-service password change API (AUTH_DB); must stay reachable while
+        # ``password_must_change`` is set, same as ``ResetMyPasswordView``.
+        "CurrentUserRestApi.update_my_password",
+        "CurrentUserRestApi.get_my_password_policy",
+    }
+)

Review Comment:
   **Suggestion:** Add an explicit type annotation to `_EXEMPT_ENDPOINTS` so this newly introduced module-level variable complies with the required Python type-hinting rule. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added module-level variable in Python code, and it is not annotated with a type hint. That matches the stated rule requiring type hints for relevant new or modified variables that can be annotated.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1a15b44a4acd4204a38844251c7ad204&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1a15b44a4acd4204a38844251c7ad204&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/password_change.py
   **Line:** 65:76
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to `_EXEMPT_ENDPOINTS` so this newly introduced module-level variable complies with the required Python type-hinting rule.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7414a4afdf0d3ecc92bb2469e93e650e1592c24fdb320fd44027f97faf42d6e5&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7414a4afdf0d3ecc92bb2469e93e650e1592c24fdb320fd44027f97faf42d6e5&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import InvalidHash, VerifyMismatchError
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE = re.compile(r"^\$2[aby]\$\d{2}\$")

Review Comment:
   **Suggestion:** Add an explicit type annotation to this module-level regex constant to satisfy the type-hint requirement for relevant variables. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This new module-level variable is assigned a regex object but has no explicit type hint. The custom rule requires type hints for relevant variables that can be annotated, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7b209b7fd1a14109a26f0de195cc226f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=7b209b7fd1a14109a26f0de195cc226f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 33:33
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this module-level regex constant to satisfy the type-hint requirement for relevant variables.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9bde5b725829c2333fe79ad1460c769ba2db9bf071a8a81fc67b45295248ef2f&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9bde5b725829c2333fe79ad1460c769ba2db9bf071a8a81fc67b45295248ef2f&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import InvalidHash, VerifyMismatchError
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE = re.compile(r"^\$2[aby]\$\d{2}\$")
+_ARGON2_HASH_PREFIX = "$argon2"

Review Comment:
   **Suggestion:** Add an explicit type annotation to this module-level constant so new relevant variables consistently include type hints. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This module-level string constant is newly added and lacks a type annotation. Because it can be annotated and the rule explicitly requires type hints for relevant variables, the suggestion identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b221a34343744f9c8029744903999391&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b221a34343744f9c8029744903999391&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 34:34
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this module-level constant so new relevant variables consistently include type hints.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=22ceb664f5f33e449b687562e60b23b3f87c86610da25f972e439aef105efd33&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=22ceb664f5f33e449b687562e60b23b3f87c86610da25f972e439aef105efd33&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3596038377


##########
superset/config.py:
##########
@@ -463,6 +464,17 @@ def _try_json_readsha(filepath: str, length: int) -> str | None:
 # AUTH_REMOTE_USER : Is for using REMOTE_USER from web server
 AUTH_TYPE = AUTH_DB
 
+# Consolidated database-auth (AUTH_DB) password and security tuning.
+# ``superset.utils.auth_db_password.get_merged_auth_db_config()`` merges
+# ``AUTH_DB_DEFAULTS`` with any ``AUTH_DB_CONFIG`` dict from
+# ``superset_config`` (partial overrides supported).
+AUTH_DB_CONFIG: dict[str, Any] = dict(AUTH_DB_DEFAULTS)
+
+# How often (seconds) to re-check the session auth stamp against the database on
+# read-only requests (GET/HEAD/OPTIONS). State-changing requests always
+# revalidate. Set to 0 to check on every authenticated request.
+SESSION_AUTH_STAMP_REVALIDATION_SECONDS: int = 300

Review Comment:
   **Suggestion:** Setting the default revalidation interval to 300 seconds introduces a security window where stale sessions can keep passing GET/HEAD/OPTIONS checks without a DB lookup. This is especially problematic for password changes performed outside the self-service flow (for example admin resets), where the stamp is bumped but the shared stamp cache is not immediately refreshed, allowing old sessions to continue reading data until the interval expires. Use a default of 0 (always revalidate) or ensure every stamp bump path updates the shared cache immediately. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   ❌ Admin ResetPasswordView leaves compromised sessions valid during TTL.
   ❌ GET /api/v1/me/ still authorized after admin reset.
   ⚠️ Cross-node session invalidation delayed for AUTH_DB admin resets.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset with AUTH_DB enabled and the default configuration in
   `superset/config.py` lines 465–476, where `AUTH_TYPE = AUTH_DB` and
   `SESSION_AUTH_STAMP_REVALIDATION_SECONDS: int = 300` is set.
   
   2. Log in as an AUTH_DB user to create a session stamp; on successful login,
   `SupersetSecurityManager.on_user_login` in `superset/security/manager.py:1232–1241` calls
   `sync_session_auth_stamp_on_login(user)` from
   `superset/utils/auth_session_stamp.py:153–165`, which writes the stamp into the signed
   session cookie, marks it validated via `_mark_session_stamp_validated` at
   `auth_session_stamp.py:261–265`, and caches it with `_cache_user_session_stamp`.
   
   3. As an administrator, reset this same user’s password via the FAB “Reset Password” flow
   (ResetPasswordView), which routes through `SupersetSecurityManager.reset_password` in
   `superset/security/manager.py:1161–1218`; when `AUTH_TYPE == AUTH_DB`, this method calls
   `bump_user_session_auth_stamp(target_user_id)` at `auth_session_stamp.py:172–194` to
   rotate the DB-stored stamp but does not call `cache_user_session_auth_stamp`
   (`auth_session_stamp.py:167–170`), leaving the shared cache and existing browser sessions
   with the old stamp.
   
   4. Within 300 seconds of the last stamp validation, have the affected user reuse their
   existing browser session to issue a read-only request such as `GET /api/v1/me/`, handled
   by `CurrentUserRestApi.get_me` in `superset/views/users/api.py:192–217`; the
   `before_request` hook `_validate_user_session_auth_stamp` in
   `auth_session_stamp.py:108–111` invokes `validate_session_auth_stamp_for_request`
   (`auth_session_stamp.py:300–382`), which consults `_should_skip_stamp_db_lookup` at
   `auth_session_stamp.py:232–258`. Because `SESSION_AUTH_STAMP_REVALIDATION_SECONDS` is 300,
   the session’s `SESSION_AUTH_STAMP_VALIDATED_AT_KEY` timestamp is recent, and the cached
   stamp still matches the old session stamp (no cache bump happened in `reset_password`),
   `_should_skip_stamp_db_lookup` returns `True` and the DB lookup is skipped, allowing the
   stale session to continue reading data until the revalidation interval and cache TTL
   expire.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e98cad2ff3b74a53ba70925058633fd4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e98cad2ff3b74a53ba70925058633fd4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/config.py
   **Line:** 476:476
   **Comment:**
   	*Security: Setting the default revalidation interval to 300 seconds introduces a security window where stale sessions can keep passing GET/HEAD/OPTIONS checks without a DB lookup. This is especially problematic for password changes performed outside the self-service flow (for example admin resets), where the stamp is bumped but the shared stamp cache is not immediately refreshed, allowing old sessions to continue reading data until the interval expires. Use a default of 0 (always revalidate) or ensure every stamp bump path updates the shared cache immediately.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=fd84ff003f88fe891d501146bf8a2052a052ffd13353e86eda0529209c092341&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=fd84ff003f88fe891d501146bf8a2052a052ffd13353e86eda0529209c092341&reaction=dislike'>👎</a>



##########
superset/security/manager.py:
##########
@@ -4551,6 +4596,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )
+            logger.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)

Review Comment:
   **Suggestion:** The failed-login timing equalization path ignores the attacker-supplied password and always checks the literal string `"password"` against a Werkzeug hash. Because real-user checks use `verify_auth_db_password(user.password, password)` (bcrypt/argon2/legacy) while missing-user checks use a different verifier and fixed input, login timing can diverge enough to leak account existence. Use the same candidate password and the same verification path for both branches (or a fake hash compatible with the same verifier) to avoid user-enumeration side channels. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ AUTH_DB login leaks account existence via timing variance.
   - ⚠️ Enables username enumeration with crafted long passwords.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Inspect AUTH_DB login helper `auth_user_db()` in
   `superset/security/manager.py:4599-131` (loaded via BulkRead), which authenticates
   database users when `AUTH_TYPE` = `AUTH_DB`.
   
   2. For a missing or inactive user, follow the branch at `manager.py:113-125`: the code
   calls `check_password_hash(current_app.config.get("AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
   DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK), "password")`, using the literal `"password"`
   instead of the attacker-supplied candidate.
   
   3. For an existing active user, follow the branch at `manager.py:126-131`: it calls
   `verify_auth_db_password(user.password, password)` with the attacker-supplied `password`.
   In `superset/utils/auth_db_password_hash.py:72-103`, the bcrypt path (`lines 81-88`)
   encodes the candidate and short-circuits to `False` when `len(encoded) >
   BCRYPT_MAX_PASSWORD_BYTES`.
   
   4. Configure AUTH_DB to use bcrypt (default in
   `superset/utils/auth_db_password.py:29-42`), create a real user with a bcrypt hash, and
   compare login timings for that username versus a non-existent username using an overlong
   password (>72 UTF-8 bytes): the real-user path for bcrypt short-circuits quickly, while
   the missing-user path always runs the full PBKDF2-based `check_password_hash` on
   `"password"`, producing measurable timing differences that can be used to infer account
   existence.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b1fd0bc59f7a47248cf161fdba705303&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b1fd0bc59f7a47248cf161fdba705303&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/manager.py
   **Line:** 4615:4621
   **Comment:**
   	*Security: The failed-login timing equalization path ignores the attacker-supplied password and always checks the literal string `"password"` against a Werkzeug hash. Because real-user checks use `verify_auth_db_password(user.password, password)` (bcrypt/argon2/legacy) while missing-user checks use a different verifier and fixed input, login timing can diverge enough to leak account existence. Use the same candidate password and the same verification path for both branches (or a fake hash compatible with the same verifier) to avoid user-enumeration side channels.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=92262aa65dc14a0da71abc2681ba54163424aa857944dce27b5fb63d2647a763&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=92262aa65dc14a0da71abc2681ba54163424aa857944dce27b5fb63d2647a763&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,265 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,

Review Comment:
   **Suggestion:** These lockout/reset policy keys are introduced as configurable behavior but are not consumed anywhere in the backend auth flow, so changing them has no effect at runtime. This creates a misleading security contract for operators who expect lockout and reset controls to be enforced. Either wire these settings into the corresponding authentication/reset logic or remove them until enforcement is implemented. [incomplete implementation]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ⚠️ Operators misled; lockout settings silently ineffective.
   - ⚠️ Increased risk of password-guessing without configured lockout.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Inspect `AUTH_DB_DEFAULTS` in `superset/utils/auth_db_password.py:29-42` (BulkRead
   output): it introduces `reset_token_expiry_minutes`, `reset_rate_limit`,
   `login_rate_limit`, `login_max_failures`, and `login_lockout_duration_minutes` as
   configurable AUTH_DB policy options.
   
   2. Use Grep across `/workspace/superset` (tool results included) for each key:
   `login_max_failures`, `reset_rate_limit`, `reset_token_expiry_minutes`, and
   `login_lockout_duration_minutes` only appear in `auth_db_password.py:29-42` and are never
   referenced elsewhere; `login_rate_limit` is used via
   `get_auth_db_login_rate_limit_string()` in `superset/views/users/api.py:40-46,88-91` to
   rate-limit `PUT /api/v1/me/password`, confirming the other knobs are truly unused.
   
   3. Inspect the AUTH_DB login flow in `superset/security/manager.py:4599-131`:
   `auth_user_db()` calls `find_user()` and `verify_auth_db_password()` to decide success and
   uses `update_user_auth_stat()` on failure, but no code reads `login_max_failures` or
   `login_lockout_duration_minutes`, so failed-login counts and lockout duration are not
   enforced.
   
   4. Inspect the password change API in `superset/views/users/api.py:340-64`: it loads the
   request body, verifies the current password via `verify_auth_db_password()`, validates and
   hashes the new password, and commits the change, but never consults
   `reset_token_expiry_minutes` or `reset_rate_limit`. As a result, operators can set these
   AUTH_DB_CONFIG values expecting lockout or reset semantics, yet the backend
   authentication/reset flows ignore them and system behavior does not change.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=45d83ec48b484fd9b60837f9654c93e9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=45d83ec48b484fd9b60837f9654c93e9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 37:41
   **Comment:**
   	*Incomplete Implementation: These lockout/reset policy keys are introduced as configurable behavior but are not consumed anywhere in the backend auth flow, so changing them has no effect at runtime. This creates a misleading security contract for operators who expect lockout and reset controls to be enforced. Either wire these settings into the corresponding authentication/reset logic or remove them until enforcement is implemented.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6c82842cc9033d0ea5bafc95f5c5d06d3cba102b29ecdb6b0c0359ad983fe19e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6c82842cc9033d0ea5bafc95f5c5d06d3cba102b29ecdb6b0c0359ad983fe19e&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4302238746

   > I see you referenced SIP 201: #37927 thanks for connecting the dots there and referencing an agreed-upon plan! Did you coordinate this work with @EnxDev who wrote that SIP? I wonder if they are working on this too?
   
   Hi, thanks @shantanukhond for getting this started. I’m currently focused on other priorities, but as soon as I have some availability I plan to move this SIP forward. In the meantime, we can sync.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3442973649


##########
superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:
##########
@@ -0,0 +1,52 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add user_session_auth_stamp table
+
+Revision ID: c6219cac9270
+Revises: b7e4f2a891c3
+Create Date: 2026-05-13 12:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "c6219cac9270"
+down_revision = "b7e4f2a891c3"

Review Comment:
   **Suggestion:** The migration references a `down_revision` that does not exist in this repository, so Alembic cannot build the revision graph and upgrades will fail with a missing-revision error. Point `down_revision` to an actual existing predecessor revision ID. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ `superset db upgrade` fails with Alembic missing revision error.
   - ❌ App initialization stops before sync_config_to_db runs successfully.
   - ⚠️ Pending migrations prevent new features relying on this table.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Inspect the new migration file
   `superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py`,
   where the Alembic identifiers are defined (`revision = "c6219cac9270"` and `down_revision
   = "b7e4f2a891c3"` at lines 29–31, confirmed via Read).
   
   2. List and search all existing migration scripts under `superset/migrations/versions/`
   (as in the LS output and a Grep for `b7e4f2a891c3`); note that no file name or file
   content in this directory defines `revision = "b7e4f2a891c3"`, meaning this parent
   revision does not exist in the current repository.
   
   3. Start Superset so that initialization runs `self.superset_app.sync_config_to_db()` from
   `superset/initialization/__init__.py:635`, which calls `_is_database_up_to_date()` in
   `superset/app.py:119-129`; this method builds an Alembic `ScriptDirectory` with
   `script_location="superset:migrations"` and calls `script.get_current_head()`, forcing
   Alembic to load the revision graph from `superset/migrations/versions/`.
   
   4. When Alembic processes the new migration with `revision = "c6219cac9270"` and
   `down_revision = "b7e4f2a891c3"` (lines 29–31 of the migration file), it cannot find a
   revision with ID `b7e4f2a891c3` in the script directory, causing a missing-revision error
   while building the revision graph; `_is_database_up_to_date()` catches the resulting
   exception and returns `False`, so `sync_config_to_db()` logs "Pending database migrations:
   run 'superset db upgrade'" at `superset/app.py:168`, and any attempt to actually run
   `superset db upgrade` (using the Alembic environment in
   `superset/migrations/env.py:64-85,87-137`) will also fail with an Alembic "no such
   revision b7e4f2a891c3" error.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=514e5448ddbc4076ad88c3fc6114ef04&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=514e5448ddbc4076ad88c3fc6114ef04&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py
   **Line:** 31:31
   **Comment:**
   	*Api Mismatch: The migration references a `down_revision` that does not exist in this repository, so Alembic cannot build the revision graph and upgrades will fail with a missing-revision error. Point `down_revision` to an actual existing predecessor revision ID.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=501bbb9f909d8b3b90493a801a5c9612bc1f4d5ff60be8515336137d04fd6237&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=501bbb9f909d8b3b90493a801a5c9612bc1f4d5ff60be8515336137d04fd6237&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/users/utils.ts:
##########
@@ -19,8 +19,34 @@
 import { t } from '@apache-superset/core/translation';
 import { SupersetClient } from '@superset-ui/core';
 import { SelectOption } from 'src/components/ListView';
+import type { UserObject } from 'src/pages/UsersList/types';
 import { FormValues } from './types';
 
+/** Fields required for PUT `/api/v1/security/users/:id` when changing password only. */
+export function buildSecurityUserUpdatePayload(user: UserObject): FormValues {
+  return {
+    first_name: user.first_name,
+    last_name: user.last_name,
+    username: user.username,
+    email: user.email,
+    active: user.active,
+    roles: (user.roles ?? []).map(r => r.id),
+    groups: (user.groups ?? []).map(g => g.id),
+  };
+}
+
+export const resetUserPassword = async (
+  userId: number,
+  password: string,
+): Promise<void> => {
+  await SupersetClient.put({
+    endpoint: `/api/v1/security/users/${userId}`,
+    jsonPayload: {
+      password,
+    },
+  });
+};

Review Comment:
   **Suggestion:** The admin reset-password request sends only `password` to `PUT /api/v1/security/users/:id`, but this endpoint expects the full user update payload in this codebase flow. As written, password resets can fail with validation errors (or overwrite behavior inconsistently) because required user fields are omitted. Build the payload from the current user (using the new helper) and include `password` in the same request body. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Users list "Reset password" action returns backend error.
   - ❌ Admins cannot reset passwords via Users List UI.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset with this PR code and log in as an admin user, then navigate to the
   Users list page implemented in `superset-frontend/src/pages/UsersList/index.tsx:20-37`,
   where `useListViewResource('security/users', ...)` powers the listing.
   
   2. In the Users list actions column
   (`superset-frontend/src/pages/UsersList/index.tsx:5-31` in the 332–391 block), click the
   "Reset password" action (`label: 'user-list-reset-password-action'`), which runs
   `handleResetPassword` that sets `currentUser` and calls
   `openModal(ModalType.RESET_PASSWORD)`, causing `UserListResetPasswordModal` to render
   (`index.tsx:568-580`).
   
   3. In `UserListResetPasswordModal`
   (`superset-frontend/src/features/users/UserListResetPasswordModal.tsx:80-107`), enter a
   new password and confirmation and submit; the modal's `handleFormSubmit` calls
   `resetUserPassword(user.id, rest.password)` at line 90, passing only the user id and the
   new password to the utility.
   
   4. The `resetUserPassword` implementation in
   `superset-frontend/src/features/users/utils.ts:38-47` performs `SupersetClient.put` to
   `/api/v1/security/users/${userId}` with `jsonPayload: { password }` only, but the OpenAPI
   spec for `PUT /api/v1/security/users/{pk}`
   (`docs/static/resources/openapi.json:35582-159`) documents a `SupersetUserApi.put` schema
   expecting a full user update payload (e.g., `active`, `email`, `first_name`, `last_name`,
   `roles`, `groups`, `username`, plus `password`), and the same endpoint is used for normal
   edits via `updateUser(user.id, values)` with full form values in `UserListModal`
   (`superset-frontend/src/features/users/UserListModal.tsx:45-51`). As a result, sending
   only `{ password }` can trigger backend validation errors or unintended overwrites of
   other fields, causing the admin password reset API call to fail and the UI to show an
   error toast instead of successfully updating the password.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=2ff77683ea164e3bb74caecd819f3ed2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=2ff77683ea164e3bb74caecd819f3ed2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/utils.ts
   **Line:** 38:48
   **Comment:**
   	*Api Mismatch: The admin reset-password request sends only `password` to `PUT /api/v1/security/users/:id`, but this endpoint expects the full user update payload in this codebase flow. As written, password resets can fail with validation errors (or overwrite behavior inconsistently) because required user fields are omitted. Build the payload from the current user (using the new helper) and include `password` in the same request body.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9a2e7e3861bae56abadf1214c58219c92051ffcc46eaa1ee32803a58ecf3e5ed&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9a2e7e3861bae56abadf1214c58219c92051ffcc46eaa1ee32803a58ecf3e5ed&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3442975514


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,263 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    crypto.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(
+  policy: AuthDbPasswordPolicy,
+): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+function satisfiesAuthDbPasswordPolicy(
+  password: string,
+  policy: AuthDbPasswordPolicy,
+): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  return Object.values(checks).every(Boolean);
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(password: string): boolean {
+  return satisfiesAuthDbPasswordPolicy(password, AUTH_DB_DEFAULT_PASSWORD_POLICY);
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = Math.max(1, Number(policy.password_min_length) || 1);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\d/.test(password),

Review Comment:
   **Suggestion:** The digit check uses JavaScript `\d`, which only matches ASCII digits, but the backend validator uses Python `\d` (Unicode-aware by default). This creates a frontend/backend contract mismatch where some passwords accepted by the API are blocked in the UI. Align the frontend digit rule with backend behavior (e.g., Unicode numeric category) so validation is consistent. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Valid Unicode-digit passwords rejected by frontend validation.
   - ⚠️ Password rules differ between AUTH_DB backend and UI.
   - ⚠️ Users see spurious "must contain digit" errors.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run Superset with `AUTH_TYPE` set to `AUTH_DB`, so the password change API `PUT
   /api/v1/me/password` is enabled; the backend handler `update_my_password` in
   `superset/views/users/api.py:42-47` enforces this and calls `validate_auth_db_password` at
   `superset/views/users/api.py:53-56`.
   
   2. From the UI, open a password change flow that uses the shared AUTH_DB password policy
   helpers, such as the self-service `ChangePasswordModal` (rendered from `UserInfo` in
   `src/pages/UserInfo/index.tsx:126-170` and implemented in
   `src/features/userInfo/UserInfoModal.tsx:145-235`) or the admin
   `UserListResetPasswordModal` in
   `src/features/users/UserListResetPasswordModal.tsx:49-107`, both of which call
   `getAuthDbPasswordPolicyError` from `src/utils/generateAuthDbPassword.ts:205-234` via a
   FormItem `validator`.
   
   3. In the modal, enter a candidate password that contains a Unicode digit but no ASCII
   digits, for example `Abcdefg!٤` (where `٤` is an Arabic-Indic digit); the frontend `digit`
   check in `getAuthDbPasswordPolicyChecks` uses the JavaScript regex `/\d/` on line 195 of
   `src/utils/generateAuthDbPassword.ts`, which only recognizes ASCII `0-9`, so `digit` is
   `false`, `getAuthDbPasswordPolicyError` returns "Password must contain at least one
   digit." (lines 223-225), and the form `validator` in `UserInfoModal.tsx:159-175` or
   `UserListResetPasswordModal.tsx:132-148` rejects the submission with a validation error.
   
   4. On the backend, the AUTH_DB validator `validate_auth_db_password` in
   `superset/utils/auth_db_password.py:175-187` uses Python's `re.search(r"\d", password)`
   (line 185), where `\d` is Unicode-aware and matches the non-ASCII digit `٤`, so the same
   password satisfies the digit requirement server-side and would be accepted by `PUT
   /api/v1/me/password` (`superset/views/users/api.py:48-56`) if sent, demonstrating a
   concrete frontend/backend contract mismatch.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=dd733e1c683642618ebbf19f3db5506c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=dd733e1c683642618ebbf19f3db5506c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 195:195
   **Comment:**
   	*Api Mismatch: The digit check uses JavaScript `\d`, which only matches ASCII digits, but the backend validator uses Python `\d` (Unicode-aware by default). This creates a frontend/backend contract mismatch where some passwords accepted by the API are blocked in the UI. Align the frontend digit rule with backend behavior (e.g., Unicode numeric category) so validation is consistent.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b628454935513927b58f0bbac18c01d58832720a2da7a53a01d26e743ab06c35&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b628454935513927b58f0bbac18c01d58832720a2da7a53a01d26e743ab06c35&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4770428592

   @shantanukhond dropping `auth_audit_log` left the new `c6219cac9270` migration with `down_revision = "b7e4f2a891c3"`, but that revision no longer exists on the branch, so `superset db upgrade` will fail to locate it. Can you re-point `down_revision` at the current head? And let's keep this to just the `/me/password` change. You can pull the admin reset (`UserListResetPasswordModal`, the `SupersetUserApi.pre_update` password path) into a follow-up so we land Pillar 1 cleanly. Needs a rebase too once that's sorted.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3460090347


##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,216 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:

Review Comment:
   **Suggestion:** Add a docstring describing the wrong-current-password scenario and expected API response. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This newly added method has no docstring.
   Since the rule requires docstrings on newly added Python functions and classes, the suggestion is valid and verified.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1e33659c5a9743bd894fd74c8c7238b4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1e33659c5a9743bd894fd74c8c7238b4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 125:125
   **Comment:**
   	*Custom Rule: Add a docstring describing the wrong-current-password scenario and expected API response.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d232012f1d8648a1c562c41bffd158cff04a76d2d12d5b305a53a76c0fc8e665&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d232012f1d8648a1c562c41bffd158cff04a76d2d12d5b305a53a76c0fc8e665&reaction=dislike'>👎</a>



##########
superset/views/users/api.py:
##########
@@ -14,47 +14,163 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
+import functools
+import logging
 from datetime import datetime
-from typing import Any, Dict
+from typing import Any, Callable, Dict
 
-from flask import current_app as app, g, redirect, request, Response
+from flask import current_app as app, g, redirect, request, Response, session
 from flask_appbuilder.api import expose, permission_name, safe
+from flask_appbuilder.const import AUTH_DB
 from flask_appbuilder.security.decorators import protect
 from flask_appbuilder.security.sqla.models import User
+from flask_login import login_user
 from marshmallow import ValidationError
+from sqlalchemy.exc import SQLAlchemyError
 from sqlalchemy.orm.exc import NoResultFound
-from werkzeug.security import generate_password_hash
 
 from superset import is_feature_enabled
 from superset.daos.user import UserDAO
-from superset.extensions import db, event_logger
+from superset.extensions import db, event_logger, security_manager
+from superset.utils.auth_db_password import (
+    get_auth_db_login_rate_limit_string,
+    get_public_auth_db_password_policy,
+    validate_auth_db_password,
+)
+from superset.utils.auth_db_password_hash import (
+    hash_auth_db_password,
+    verify_auth_db_password,
+)
+from superset.utils.auth_session_stamp import (
+    bump_user_session_auth_stamp,
+    clear_flask_login_remember_cookie,
+)
 from superset.utils.slack import get_user_avatar, SlackClientError
 from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics
-from superset.views.users.schemas import CurrentUserPutSchema, UserResponseSchema
+from superset.views.users.schemas import (
+    CurrentUserPasswordPutSchema,
+    CurrentUserPutSchema,
+    UserResponseSchema,
+)
 from superset.views.utils import bootstrap_user_data
 
+logger = logging.getLogger(__name__)
+
 user_response_schema = UserResponseSchema()
 
 
+def _get_client_ip() -> str | None:
+    """Return best-effort client IP from request context."""
+    if request.access_route:
+        return request.access_route[0]
+    return request.remote_addr
+
+
+def _me_password_rate_limit_key() -> str:
+    uid = getattr(getattr(g, "user", None), "id", None)
+    if uid is not None:
+        return f"me_password_uid:{uid}"
+    return _get_client_ip() or "unknown"

Review Comment:
   **Suggestion:** Add an inline docstring to this newly introduced helper function to document what key it generates and when it falls back to client IP. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly introduced Python function and it has no docstring in the final file state.
   The custom rule requires new functions and classes to be documented inline, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=3ff516ab49de4c8aa046ff5a3ba3f401&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=3ff516ab49de4c8aa046ff5a3ba3f401&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 71:75
   **Comment:**
   	*Custom Rule: Add an inline docstring to this newly introduced helper function to document what key it generates and when it falls back to client IP.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ff09fa8120b17e92561b658809d455a066ffa604ee49415546a26923f0f7b4d5&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ff09fa8120b17e92561b658809d455a066ffa604ee49415546a26923f0f7b4d5&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,216 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:

Review Comment:
   **Suggestion:** Add an inline docstring to explain that this test verifies weak password validation failure. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The method is newly added and lacks a docstring.
   This matches the stated rule for documenting new Python functions, so the suggestion is verified.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=6edf0bd1d30348bf86a23224479e1720&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=6edf0bd1d30348bf86a23224479e1720&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 139:139
   **Comment:**
   	*Custom Rule: Add an inline docstring to explain that this test verifies weak password validation failure.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c53bdc23e01f84e78d08514ac55598b71e5361f14881dd153268f35752c166e5&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c53bdc23e01f84e78d08514ac55598b71e5361f14881dd153268f35752c166e5&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,216 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:

Review Comment:
   **Suggestion:** Add a concise docstring stating that this test covers the successful password change flow. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added test method with no inline docstring present.
   The custom rule requires new Python functions/classes to be documented, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e4beb5464ee74d948ee1e8895b13ce0d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e4beb5464ee74d948ee1e8895b13ce0d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 153:153
   **Comment:**
   	*Custom Rule: Add a concise docstring stating that this test covers the successful password change flow.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=619449da443ad6652b7faa8b419866eb17b3f11c7e322f2832755e7ac414268e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=619449da443ad6652b7faa8b419866eb17b3f11c7e322f2832755e7ac414268e&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,216 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")
+        assert session_cookie is not None
+
+        client_b: FlaskClient = app.test_client()
+        client_b.set_cookie(
+            key="session",
+            value=session_cookie.value,
+            domain=session_cookie.domain or "localhost",
+            path=session_cookie.path or "/",
+        )
+
+        new_password = "AnotherStr0ng!PassClone"  # noqa: S105
+        try:
+            rv = client_a.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            assert client_b.get(meUri).status_code == 401
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_clears_remember_cookie(self) -> None:
+        """
+        Password change schedules Flask-Login remember-me cookie deletion.
+
+        Superset does not expose remember-me in the React login UI; this is defensive
+        hardening for FAB / Flask-Login persistent cookies.
+        """
+        app = superset_integration_app
+        remember_name = app.config.get("REMEMBER_COOKIE_NAME", "remember_token")
+        self.login(ADMIN_USERNAME)
+        self.client.set_cookie(remember_name, "stale-remember-token")
+
+        new_password = "AnotherStr0ng!PassRemember"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            set_cookies = rv.headers.getlist("Set-Cookie")
+            cleared = any(
+                remember_name in header
+                and ("=;" in header or "Max-Age=0" in header or "1970" in header)
+                for header in set_cookies
+            )
+            assert cleared, f"Expected remember cookie clear in {set_cookies}"
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_invalid_hash_algorithm(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth_db_config = superset_integration_app.config.get(
+            "AUTH_DB_CONFIG", {}
+        )
+        try:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = {
+                **original_auth_db_config,
+                "password_hash_algorithm": "invalid",
+            }
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = original_auth_db_config
+
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "password_hash_algorithm" in data["message"]
+
+    def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth = superset_integration_app.config["AUTH_TYPE"]
+        try:
+            superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_TYPE"] = original_auth
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_get_my_password_policy_success(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.get("/api/v1/me/password/policy")
+        assert rv.status_code == 200
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["result"] == get_public_auth_db_password_policy()
+
+    def test_get_my_password_policy_unavailable_when_not_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth = superset_integration_app.config["AUTH_TYPE"]
+        try:
+            superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+            rv = self.client.get("/api/v1/me/password/policy")
+        finally:
+            superset_integration_app.config["AUTH_TYPE"] = original_auth
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_confirmation_mismatch(self) -> None:

Review Comment:
   **Suggestion:** Add a docstring that explains this test checks mismatch handling between new and confirmation passwords. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The final file state shows this newly added function without a docstring.
   That directly violates the rule requiring docstrings on new Python functions and classes.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e440a0d5686e45af8addb2ad46cd75b9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e440a0d5686e45af8addb2ad46cd75b9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 314:314
   **Comment:**
   	*Custom Rule: Add a docstring that explains this test checks mismatch handling between new and confirmation passwords.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=90edcce2a5e39533060a96783f3dea09b34806f893603bb1f97e76b787444e1f&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=90edcce2a5e39533060a96783f3dea09b34806f893603bb1f97e76b787444e1f&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,216 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:

Review Comment:
   **Suggestion:** Add a short docstring to this newly added test method to document the expected behavior being validated. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added Python test method and it has no docstring in the final file state.
   The custom rule requires newly added Python functions/classes to include docstrings, so the suggestion identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e7b24c3050eb404083f73b6d7b9ae6ae&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e7b24c3050eb404083f73b6d7b9ae6ae&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 118:118
   **Comment:**
   	*Custom Rule: Add a short docstring to this newly added test method to document the expected behavior being validated.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=0ce1cdb5a8ff4f61e3fb31fa16e3b3543b3a803c26c9296cf1906ebe663cf304&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=0ce1cdb5a8ff4f61e3fb31fa16e3b3543b3a803c26c9296cf1906ebe663cf304&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4770495280

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3474407364


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,287 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/**
+ * Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in
+ * ``superset/utils/auth_db_password.py``. Sync is enforced by
+ * ``tests/unit_tests/utils/test_auth_db_password.py``.
+ */
+export const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+/** Mirrors ``int(cfg.get("password_min_length", default))`` in auth_db_password.py. */
+function resolveAuthDbPasswordMinLength(
+  passwordMinLength: number | undefined | null,
+): number {
+  if (passwordMinLength === undefined || passwordMinLength === null) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  const parsed = Number(passwordMinLength);
+  if (!Number.isFinite(parsed)) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  return Math.trunc(parsed);
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const cryptoObj = globalThis.crypto;
+  if (!cryptoObj?.getRandomValues) {
+    throw new Error(
+      'secureRandomInt: a Web Crypto implementation with getRandomValues ' +
+        'is required to generate passwords',
+    );
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    cryptoObj.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(policy: AuthDbPasswordPolicy): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\p{Nd}/u.test(password),
+    special:
+      !policy.password_require_special || /[^\p{L}\p{N}\s]/u.test(password),
+    commonPassword:
+      !policy.password_common_list_check ||
+      !AUTH_DB_COMMON_PASSWORDS.has(password.toLowerCase().trim()),
+  };
+}
+
+function satisfiesAuthDbPasswordPolicy(
+  password: string,
+  policy: AuthDbPasswordPolicy,
+): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  return Object.values(checks).every(Boolean);
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(
+  password: string,
+): boolean {
+  return satisfiesAuthDbPasswordPolicy(
+    password,
+    AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  );
+}
+
+/** Returns the first validation error for a password under the given policy. */
+export function getAuthDbPasswordPolicyError(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): string | null {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  if (!checks.minLength) {
+    return t(
+      'Password must be at least %s characters long.',
+      resolveAuthDbPasswordMinLength(policy.password_min_length),
+    );
+  }
+  if (policy.password_require_uppercase && !checks.uppercase) {
+    return t('Password must contain at least one uppercase letter.');
+  }
+  if (policy.password_require_lowercase && !checks.lowercase) {
+    return t('Password must contain at least one lowercase letter.');
+  }
+  if (policy.password_require_digit && !checks.digit) {
+    return t('Password must contain at least one digit.');
+  }
+  if (policy.password_require_special && !checks.special) {
+    return t(
+      'Password must contain at least one special character (not a letter, digit, or space).',
+    );
+  }
+  if (policy.password_common_list_check && !checks.commonPassword) {
+    return t('This password is too common. Choose a stronger password.');
+  }
+  return null;
+}
+
+/**
+ * Returns a random password that should pass ``validate_auth_db_password`` for the
+ * supplied policy (defaults to server ``AUTH_DB_CONFIG`` defaults).
+ */
+export function generateAuthDbPassword(
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): string {
+  const minLen = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  const requiredPools = getRequiredCharacterPools(policy);
+  const generationPool = getGenerationPool(policy);
+  const maxAttempts = 64;
+  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
+    const chars: string[] = requiredPools.map(pool => pick(pool));
+    while (getCodePointLength(chars.join('')) < minLen) {
+      chars.push(pick(generationPool));
+    }

Review Comment:
   **Suggestion:** Password generation can return an empty string when the policy allows zero minimum length and no required character classes, because no characters are seeded and the fill loop never runs. This breaks the password form flow (required field) and produces an unusable generated password. Ensure generation always emits at least one character (or enforce a generation floor independent of policy). [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Generated password may be empty under len-zero policy.
   - ⚠️ Password reset UI shows errors after using generator.
   - ⚠️ Admins cannot rely on generator for relaxed policies.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure AUTH_DB to allow empty passwords by setting a zero minimum length and
   disabling all character class checks, e.g. in `superset_config.py` use the policy from the
   backend test `test_validate_auth_db_password_accepts_any_length_when_min_length_is_zero`
   at `superset/tests/unit_tests/utils/test_auth_db_password.py:102-112` (min length 0, all
   `password_require_*` flags false, `password_common_list_check` false); this is treated as
   valid by `validate_auth_db_password()` at `superset/utils/auth_db_password.py:188-229`.
   
   2. Sign in with AUTH_DB and open the self-service password reset modal in the UI, which
   uses `UserInfoModal` at `superset-frontend/src/features/userInfo/UserInfoModal.tsx:45-52`;
   when the modal is shown in reset mode (`!isEditMode`), it fetches the active policy from
   `/api/v1/me/password/policy` (lines 57-69) and stores it in `passwordPolicy` state as an
   `AuthDbPasswordPolicy`.
   
   3. In the reset modal, click the "Generate password" control rendered by
   `GeneratePasswordInputSuffix` at
   `superset-frontend/src/components/GeneratePasswordInputSuffix.tsx:29-37`, which calls
   `onGenerate={() => { const pwd = generateAuthDbPassword(passwordPolicy); ... }}` in
   `UserInfoModal` at `superset-frontend/src/features/userInfo/UserInfoModal.tsx:181-189`.
   
   4. For the zero-minimum policy with all requirements disabled, `generateAuthDbPassword()`
   at `superset-frontend/src/utils/generateAuthDbPassword.ts:268-285` computes `minLen =
   resolveAuthDbPasswordMinLength(0) = 0`, `requiredPools = []`, and thus `chars` starts as
   `[]`; the `while (getCodePointLength(chars.join('')) < minLen)` loop at lines 276-279
   never executes, so `password` is the empty string `''`. `form.setFieldsValue({
   new_password: pwd, confirm_password: pwd })` at `UserInfoModal.tsx:183-188` sets both
   fields to empty, immediately violating the `required: true` rule on `new_password` at
   lines 159-163 and causing the "New password is required" validation error. The generator
   has produced an unusable empty password in a configuration that backend validation
   explicitly supports.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1e05df562a1241d2b24836e78e9136fc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1e05df562a1241d2b24836e78e9136fc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 276:279
   **Comment:**
   	*Logic Error: Password generation can return an empty string when the policy allows zero minimum length and no required character classes, because no characters are seeded and the fill loop never runs. This breaks the password form flow (required field) and produces an unusable generated password. Ensure generation always emits at least one character (or enforce a generation floor independent of policy).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=2cc96d1012057579c462dac30231511a78b3eb841e46bc8e4e22957f9b0bfc57&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=2cc96d1012057579c462dac30231511a78b3eb841e46bc8e4e22957f9b0bfc57&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4842299445

   ## EnxDev's Review Agent — apache/superset#39469 · HEAD 90160b2
   **comment** — Pillar 1 is in good shape; the earlier session-clear-before-commit blocker is resolved. Remaining items are a CSRF-rotation edge case, a per-request DB query now added for *all* auth types, and minor cleanup.
   
   This updates my [earlier review at `f4d4cd6`](https://github.com/apache/superset/pull/39469#pullrequestreview-4572827197); the audit-log infra has since been dropped (good — matches the Pillar-1 split @rusackas/@dpgaspar asked for), and the commit/session ordering bug is fixed. Re-reviewed the current diff fresh, so older bot/maintainer threads are largely stale.
   
   ### 🔴 Functional
   - **`superset/views/users/api.py:2557`** (`_reestablish_login_session`) · _Low_ — clearing the whole session on a successful password change rotates the Flask-WTF CSRF token (`WTF_CSRF_ENABLED = True` by default), but the SPA keeps the token it loaded at bootstrap. The old `PUT /api/v1/me/` flow never rebuilt the session, so this is new: after changing their password in-place (no page reload), the next mutating request from the same tab can 400 on CSRF until a refresh. Worth confirming `SupersetClient` re-fetches the token on a CSRF failure; if not, return/refresh the token after this call. **regression test:** with CSRF enabled, change the password then issue a mutating request (e.g. PUT a chart) on the same client and assert it isn't rejected for a stale CSRF token.
   
   ### 🟡 Should-fix
   - **`superset/security/manager.py:937`** (`create_login_manager` → `register_session_auth_stamp_hook`) — the `before_request` stamp check and the login-time stamp row are wired in unconditionally, so every authenticated request in *every* deployment (OAuth/LDAP/remote-user, not just AUTH_DB) now does a `db.session.get(UserSessionAuthStamp, user_id)` and every login writes a row. For a feature scoped to AUTH_DB password change, that's a broad per-request cost. Gate the hook/stamp to AUTH_DB, or cache the stamp.
   - **Patch coverage 85% (33 lines uncovered, per Codecov).** The uncovered lines are the failure paths that matter most here — `PasswordChangeConflictError`/`SQLAlchemyError` 500 in `update_my_password`, and the `IntegrityError` retry branches in `auth_session_stamp.py`. Add an integration test for the optimistic-lock conflict (concurrent change → 400) so those branches are guarded.
   
   ### 🔵 Nits
   - `superset/migrations/versions/2026-05-13_12-00_c6219cac9270_...py:1634` — docstring says `Revises: 78a40c08b4be` but `down_revision = "a7d3f1b9c2e4"`. The code is correct (`a7d3f1b9c2e4` is the current head); fix the stale docstring line to match.
   - `superset/views/users/api.py:2459` (`_get_client_ip`/IP fallback in `_me_password_rate_limit_key`) — the endpoint is `@protect()`, so `g.user.id` is always present and the IP branch is dead. Harmless, but droppable.
   
   ### 🙌 Praise
   - `superset/utils/auth_db_password_hash.py` — clean bcrypt/argon2 split with legacy-Werkzeug fallback so existing users keep logging in until they rotate; the 72-byte bcrypt guard and the client/server common-password sync test (`test_common_passwords_stay_in_sync_with_frontend`) are nice touches.
   
   <!-- enxdev-review-agent:90160b2 -->
   _Reviewed by EnxDev's Review Agent — @EnxDev · HEAD 90160b2._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4901549743

   ## EnxDev's Review Agent — apache/superset#39469 · HEAD f0de405
   **request changes** — two blockers: (1) the new migration forks the Alembic tree into two heads, so every DB-dependent CI job fails at `db upgrade`; (2) the password-change endpoint still writes plaintext credentials into the `logs` table.
   
   _Re-review; supersedes my [prior review](https://github.com/apache/superset/pull/39469#issuecomment-4896628917) (HEAD 5e2a6bc). The plaintext-logging blocker below was raised there and is **not** addressed in this revision. The core password-change flow otherwise remains well built._
   
   ### 🔴 Functional
   
   - **`superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:31`** · _High_ — `down_revision = "b4a3f2e1d0c9"` targets a **mid-graph** revision, not the head. `b4a3f2e1d0c9` already has a child (`7c4a8d09ca37`, `add_deleted_at_to_slices`), so this migration becomes a second child and forks the tree into two heads. `flask db upgrade` then aborts with `Error: Multiple head revisions are present for given argument 'head'` — the exact error in the failing `test-sqlite` / `test-mysql` / `test-postgres` / `docker-build` logs, which is why the whole pipeline is red before any test runs. **Fix:** repoint `down_revision` to the current single head (`7c4a8d09ca37` today; re-run `alembic heads` after rebasing — it moves). **regression test:** the existing single-head CI guard passes once `alembic heads` returns one revision.
   
   - **`superset/views/users/api.py:318`** · _High_ — `update_my_password` is decorated with `@event_logger.log_this_with_context`. On context exit, `log_with_context` builds `payload = collect_request_payload()` (`utils/log.py:32`), which does `payload.update(request.get_json())` — merging `current_password`, `new_password`, `confirm_password` verbatim — then calls `self.log(records=[payload], …)`. The default `EVENT_LOGGER = DBEventLogger` uses `records` directly (`utils/log.py`: `curated_payload` is only a fallback `if not records`), `json.dumps`-ing each into `Log.json` and committing. So both passwords land in the metadata `logs` table in **plaintext**, on success and failure paths alike — directly contradicting this PR's own doc ("never stored in plaintext or logged", `configuring-superset.mdx`). Principal: any role with `can_read on Log` (Admin by default, but log read is commonly delegated to custom auditor roles, and `logs` is routinely shipped to external SIEMs/b
 ackups); leaking `current_password` in particular hands over a credential users often reuse elsewhere. **Fix:** drop `log_this_with_context` here and emit a manual event carrying only `{user_id, action}`, or scrub password-like keys in `collect_request_payload`. **regression test:** after a successful `PUT /me/password`, assert no `Log.json` row contains the new or current password.
   
   ### 🟡 Should-fix
   - **`superset/security/manager.py` (`reset_password`)** and **`superset/cli/reset.py`** — only the self-service path (`_commit_user_password_change`) calls `bump_user_session_auth_stamp`. Admin-initiated resets and the CLI don't rotate the stamp, so an admin resetting a *compromised* account (the case that matters) leaves the attacker's existing session valid. Bump the stamp there too, or note it as an explicit Pillar-1 follow-up.
   - **`superset/utils/auth_session_stamp.py:377`** — a signed session with no `_auth_session_stamp` is adopted to the current DB stamp rather than invalidated, so sessions authenticated *before* this feature deploys survive one password rotation. Bounded to the rollout window (the cookie is signed, so the stamp can't be stripped by an attacker); confirm that's the intended trade-off.
   
   ### 🔵 Nits
   - After repointing the migration, regenerate the file — its `2026-05-13` date/`Create Date` predates its new parent, so the chain reads out of order.
   - `sync_session_auth_stamp_on_login` fires twice per login (once from `on_user_login`, once from the `user_logged_in` signal handler) — idempotent but redundant; keep one path.
   - PR title: the breaking `PUT /api/v1/me/` password rejection warrants `feat(auth)!:` (the `risk:breaking-change` label is set; the `!` is not).
   
   ### 🙌 Praise
   - **`_commit_user_password_change`** — optimistic-concurrency `UPDATE … WHERE password = old_hash` (→ `PasswordChangeConflictError` → 400), with the hash write + stamp bump + `clear_password_must_change` all inside one nested-safe `@transaction()` (verified re-entrant), cache updated only post-commit, then session rebuilt. Correct and clean.
   - bcrypt's 72-byte truncation is guarded at the hash, verify, and policy layers, and `test_common_passwords_stay_in_sync_with_frontend` asserts the TS blocklist equals `_COMMON_PASSWORDS` — exactly the drift guard this dual-validation design needs.
   
   ---
   _Verified as non-issues so they aren't re-raised: `noop_user_update` / `get_first_user` / `update_user_auth_stat` all exist on FAB's `BaseSecurityManager` (the `auth_user_db` override runs); `AUTH_DB_FAKE_PASSWORD_HASH_CHECK` uses `.get(…, DEFAULT)` (no KeyError); frontend digit/length checks are Unicode-aware (`\p{Nd}`, code-point counting) and match the backend; the change clears `password_must_change` and the endpoint is in `_EXEMPT_ENDPOINTS`._
   
   <!-- enxdev-review-agent:f0de405 -->
   _Reviewed by EnxDev's Review Agent — @EnxDev · HEAD f0de405._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4717071760

   Back to this ticket will finish Pillar 1 in next couple of days.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3425655875


##########
superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:
##########
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add user_session_auth_stamp table
+
+Revision ID: c6219cac9270
+Revises: b7e4f2a891c3
+Create Date: 2026-05-13 12:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "c6219cac9270"
+down_revision = "b7e4f2a891c3"
+
+
+def upgrade() -> None:
+    create_table(
+        "user_session_auth_stamp",
+        sa.Column("user_id", sa.Integer(), nullable=False),
+        sa.Column("stamp", sa.String(length=36), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["ab_user.id"],
+            name="fk_user_session_auth_stamp_user_id_ab_user",
+            ondelete="CASCADE",
+        ),
+        sa.PrimaryKeyConstraint("user_id", name="pk_user_session_auth_stamp"),
+    )
+
+
+def downgrade() -> None:
+    drop_table("user_session_auth_stamp")

Review Comment:
   **Suggestion:** Add a short docstring to `downgrade` explaining that it reverts the table creation. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The rule requires newly added Python functions to include docstrings. `downgrade()` is a new function in this newly created file and has no docstring, so the suggestion correctly identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=017199733b054b4c850e77c56ca8548f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=017199733b054b4c850e77c56ca8548f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py
   **Line:** 49:50
   **Comment:**
   	*Custom Rule: Add a short docstring to `downgrade` explaining that it reverts the table creation.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3bd9589ebd091614c5269ad4c7f6a1fbb818d04c872b79e87938afc84a351811&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3bd9589ebd091614c5269ad4c7f6a1fbb818d04c872b79e87938afc84a351811&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:
##########
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add auth_audit_log table
+
+Revision ID: b7e4f2a891c3
+Revises: ce6bd21901ab
+Create Date: 2026-04-19 14:30:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "b7e4f2a891c3"
+down_revision = "ce6bd21901ab"
+
+
+def upgrade() -> None:
+    create_table(
+        "auth_audit_log",
+        sa.Column("id", sa.Integer(), nullable=False, autoincrement=True),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("event_type", sa.String(length=64), nullable=False),
+        sa.Column("ip_address", sa.String(length=256), nullable=True),
+        sa.Column("user_agent", sa.Text(), nullable=True),
+        sa.Column("metadata", sa.JSON(), nullable=True),
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["ab_user.id"],
+            name="fk_auth_audit_log_user_id_ab_user",
+        ),
+        sa.PrimaryKeyConstraint("id", name="pk_auth_audit_log"),
+    )
+
+
+def downgrade() -> None:
+    drop_table("auth_audit_log")

Review Comment:
   **Suggestion:** Add a docstring to the new rollback function so its behavior is explicitly documented alongside the migration logic. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The newly added `downgrade()` function also lacks a docstring. This violates the custom rule requiring docstrings for newly added Python functions and classes.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=41baf08209d14df1b4b085896dd7f01d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=41baf08209d14df1b4b085896dd7f01d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
   **Line:** 53:54
   **Comment:**
   	*Custom Rule: Add a docstring to the new rollback function so its behavior is explicitly documented alongside the migration logic.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=37b32ee6e7470e3d6d6d4a9263a336756c5dd9eceb97b96840efb4e6ad150d0c&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=37b32ee6e7470e3d6d6d4a9263a336756c5dd9eceb97b96840efb4e6ad150d0c&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:
##########
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""merging two heads
+
+Revision ID: 33a0aac0a26a
+Revises: ('c6219cac9270', '33d7e0e21daa')
+Create Date: 2026-06-16 16:00:00.000000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "33a0aac0a26a"
+down_revision = ("c6219cac9270", "33d7e0e21daa")
+
+
+def upgrade() -> None:
+    pass
+
+
+def downgrade() -> None:
+    pass

Review Comment:
   **Suggestion:** Add an inline docstring to this new function clarifying that this merge migration has no downgrade actions. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This new function also lacks a docstring in the final file. Since the rule requires docstrings for newly added Python functions and classes, the suggestion identifies a genuine violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=19a9a8f3b72440f28818e0347221eaac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=19a9a8f3b72440f28818e0347221eaac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py
   **Line:** 34:35
   **Comment:**
   	*Custom Rule: Add an inline docstring to this new function clarifying that this merge migration has no downgrade actions.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ce1b09c39645ee617ae3094e1db69442371c7767ccb3ff6d16debdeb5acdded6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ce1b09c39645ee617ae3094e1db69442371c7767ccb3ff6d16debdeb5acdded6&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:
##########
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add auth_audit_log table
+
+Revision ID: b7e4f2a891c3
+Revises: ce6bd21901ab
+Create Date: 2026-04-19 14:30:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "b7e4f2a891c3"
+down_revision = "ce6bd21901ab"
+
+
+def upgrade() -> None:
+    create_table(
+        "auth_audit_log",
+        sa.Column("id", sa.Integer(), nullable=False, autoincrement=True),

Review Comment:
   **Suggestion:** Replace the auto-incrementing integer primary key with a UUID-based primary key for the new audit table so new identifiers are not tied to internal sequential IDs. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The migration introduces a new table with an auto-incrementing integer primary key. This matches the custom rule against adding new database models with sequential integer IDs when a UUID-based identifier should be used instead.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b40a50bd78894847b50f0fa98b1f1be7&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b40a50bd78894847b50f0fa98b1f1be7&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
   **Line:** 37:37
   **Comment:**
   	*Custom Rule: Replace the auto-incrementing integer primary key with a UUID-based primary key for the new audit table so new identifiers are not tied to internal sequential IDs.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=133674b30dc0a226dad090783139358899b64a6452d447ce8f99cf2652915ac8&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=133674b30dc0a226dad090783139358899b64a6452d447ce8f99cf2652915ac8&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:
##########
@@ -0,0 +1,50 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add user_session_auth_stamp table
+
+Revision ID: c6219cac9270
+Revises: b7e4f2a891c3
+Create Date: 2026-05-13 12:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "c6219cac9270"
+down_revision = "b7e4f2a891c3"
+
+
+def upgrade() -> None:
+    create_table(
+        "user_session_auth_stamp",
+        sa.Column("user_id", sa.Integer(), nullable=False),
+        sa.Column("stamp", sa.String(length=36), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["ab_user.id"],
+            name="fk_user_session_auth_stamp_user_id_ab_user",
+            ondelete="CASCADE",
+        ),
+        sa.PrimaryKeyConstraint("user_id", name="pk_user_session_auth_stamp"),
+    )

Review Comment:
   **Suggestion:** Add a short docstring to `upgrade` describing the schema change it applies. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The rule requires newly added Python functions to include docstrings. `upgrade()` is a new function in a newly created migration file and has no docstring, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=60f5d3a315b74fb69c393de457c5a2c4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=60f5d3a315b74fb69c393de457c5a2c4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py
   **Line:** 34:46
   **Comment:**
   	*Custom Rule: Add a short docstring to `upgrade` describing the schema change it applies.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8f1e4654d12ee42a8d52c33bc53f5f20fd12cb94aaf9eb413094bc011f5896b8&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8f1e4654d12ee42a8d52c33bc53f5f20fd12cb94aaf9eb413094bc011f5896b8&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:
##########
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""merging two heads
+
+Revision ID: 33a0aac0a26a
+Revises: ('c6219cac9270', '33d7e0e21daa')
+Create Date: 2026-06-16 16:00:00.000000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "33a0aac0a26a"
+down_revision = ("c6219cac9270", "33d7e0e21daa")
+
+
+def upgrade() -> None:
+    pass

Review Comment:
   **Suggestion:** Add an inline docstring to this new function describing that this merge migration intentionally performs no upgrade operations. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This new function has no docstring in the final file. The rule requires newly added Python functions and classes to include docstrings, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=4dc47c1050dc4ace919a285c549e9021&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=4dc47c1050dc4ace919a285c549e9021&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py
   **Line:** 30:31
   **Comment:**
   	*Custom Rule: Add an inline docstring to this new function describing that this merge migration intentionally performs no upgrade operations.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f2edba8a8f6904f73fd21877443fb091397685a006410ec347ed8ec75de4ee73&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f2edba8a8f6904f73fd21877443fb091397685a006410ec347ed8ec75de4ee73&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "dpgaspar (via GitHub)" <gi...@apache.org>.
dpgaspar commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3428203780


##########
superset/models/auth_audit_log.py:
##########
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Authentication audit log entries for AUTH_DB flows."""
+
+from __future__ import annotations
+
+import uuid
+from datetime import datetime, timezone
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy.types import JSON
+from sqlalchemy_utils import UUIDType
+
+from superset import security_manager
+
+
+def _utc_now() -> datetime:
+    return datetime.now(timezone.utc)
+
+
+class AuthAuditLog(Model):

Review Comment:
   Would prefer to just use the existing `Log` table. Already has an adapter to send these to other storage types and pruning



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond closed pull request #39469: feat(auth): implement password change API for AUTH_DB authentication
URL: https://github.com/apache/superset/pull/39469


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106960251


##########
tests/unit_tests/utils/test_auth_db_password.py:
##########
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from marshmallow import ValidationError
+
+from superset.utils.auth_db_password import validate_auth_db_password
+
+_POLICY = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+}
+
+
+def test_validate_auth_db_password_accepts_strong_password() -> None:
+    validate_auth_db_password("GoodStr0ng!Pass", _POLICY)

Review Comment:
   **Suggestion:** Add a short docstring to this newly added test function to comply with the docstring requirement for new function definitions. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
       """Validate that a strong password passes policy checks."""
   ```
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The suggestion addresses a new test function that currently has no docstring. Adding a concise docstring satisfies the stated docstring requirement and does not change the test logic. The replacement is syntactically valid Python and uses only existing symbols.
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Futils%2Ftest_auth_db_password.py%0A%2A%2ALine%3A%2A%2A%2034%3A34%0A%2A%2AComment%3A%2A%2A%0A%09%2ACustom%20Rule%3A%20Add%20a%20short%20docstring%20to%20this%20newly%20added%20test%20function%20to%20comply%20with%20the%20docstring%20requirement%20for%20new%20function%20definitions.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Futils%2Ftest_auth_db_password.py%0A%2A%2ALine%3A%2A%2A%2034%3A34%0A%2A%2AComment%3A%2A%2A%0A%09%2ACustom%20Rule%3A%20Add%20a%20sho
 rt%20docstring%20to%20this%20newly%20added%20test%20function%20to%20comply%20with%20the%20docstring%20requirement%20for%20new%20function%20definitions.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/utils/test_auth_db_password.py
   **Line:** 34:34
   **Comment:**
   	*Custom Rule: Add a short docstring to this newly added test function to comply with the docstring requirement for new function definitions.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=94a0dbcee45cb5c6856916d062dd77f50c105693a16b8ea95a120663677ad481&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=94a0dbcee45cb5c6856916d062dd77f50c105693a16b8ea95a120663677ad481&reaction=dislike'>👎</a>



##########
superset/models/auth_audit_log.py:
##########
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Authentication audit log entries for AUTH_DB flows."""
+
+from __future__ import annotations
+
+from datetime import datetime
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy.types import JSON
+
+from superset import security_manager
+
+
+class AuthAuditLog(Model):
+    """
+    Append-only audit trail for authentication-related events.
+
+    The physical column name ``metadata`` is mapped to ``event_metadata`` because
+    ``metadata`` is reserved on SQLAlchemy declarative models.
+    """
+
+    __tablename__ = "auth_audit_log"
+
+    id = Column(Integer, primary_key=True)
+    user_id = Column(Integer, ForeignKey("ab_user.id"), nullable=True)
+    event_type = Column(String(64), nullable=False)
+    ip_address = Column(String(256), nullable=True)
+    user_agent = Column(Text, nullable=True)
+    event_metadata = Column("metadata", JSON, nullable=True)
+    created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
+
+    user = relationship(
+        security_manager.user_model,
+        foreign_keys=[user_id],
+    )
+
+    def __repr__(self) -> str:
+        return f"<AuthAuditLog id={self.id} event_type={self.event_type!r}>"

Review Comment:
   **Suggestion:** Add a docstring to the new method so all newly introduced methods include inline documentation. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   ```suggestion
           """Return a readable string representation of the audit log entry."""
   ```
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The existing code introduces a new method without an inline docstring, which matches the stated documentation rule. The improved code adds a valid docstring and keeps the method body unchanged, so it is syntactically correct and does not alter behavior.
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fmodels%2Fauth_audit_log.py%0A%2A%2ALine%3A%2A%2A%2055%3A55%0A%2A%2AComment%3A%2A%2A%0A%09%2ACustom%20Rule%3A%20Add%20a%20docstring%20to%20the%20new%20method%20so%20all%20newly%20introduced%20methods%20include%20inline%20documentation.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fmodels%2Fauth_audit_log.py%0A%2A%2ALine%3A%2A%2A%2055%3A55%0A%2A%2AComment%3A%2A%2A%0A%09%2ACustom%20Rule%3A%20Add%20a%20docstring%20to%20the%20new%20method%20so%20all%20newly%20introduced%20method
 s%20include%20inline%20documentation.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/auth_audit_log.py
   **Line:** 55:55
   **Comment:**
   	*Custom Rule: Add a docstring to the new method so all newly introduced methods include inline documentation.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=454edd80cbc539b473f3f5492085983355f67059f595ff73ec0ed475c4bc6e77&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=454edd80cbc539b473f3f5492085983355f67059f595ff73ec0ed475c4bc6e77&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "netlify[bot] (via GitHub)" <gi...@apache.org>.
netlify[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4276086241

   ### <span aria-hidden="true">✅</span> Deploy Preview for *superset-docs-preview* ready!
   
   
   |  Name | Link |
   |:-:|------------------------|
   |<span aria-hidden="true">🔨</span> Latest commit | a1339a4f88f2256648c03f665f62fc132c5f7ece |
   |<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/projects/superset-docs-preview/deploys/69e4e2a9a9455500085ced7f |
   |<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-39469--superset-docs-preview.netlify.app](https://deploy-preview-39469--superset-docs-preview.netlify.app) |
   |<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTM5NDY5LS1zdXBlcnNldC1kb2NzLXByZXZpZXcubmV0bGlmeS5hcHAifQ.FR6FXPQgN2lQuuE-po5YzoUazPWKWERIZ_7ct5qnYEA)<br /><br />_Use your smartphone camera to open QR code link._</details> |
   ---
   <!-- [superset-docs-preview Preview](https://deploy-preview-39469--superset-docs-preview.netlify.app) -->
   _To edit notification comments on pull requests, go to your [Netlify project configuration](https://app.netlify.com/projects/superset-docs-preview/configuration/notifications#deploy-notifications)._


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4276082794

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3425685445


##########
superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:
##########
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add auth_audit_log table
+
+Revision ID: b7e4f2a891c3
+Revises: ce6bd21901ab
+Create Date: 2026-04-19 14:30:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "b7e4f2a891c3"
+down_revision = "ce6bd21901ab"
+
+
+def upgrade() -> None:
+    create_table(
+        "auth_audit_log",
+        sa.Column("id", sa.Integer(), nullable=False, autoincrement=True),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("event_type", sa.String(length=64), nullable=False),
+        sa.Column("ip_address", sa.String(length=256), nullable=True),
+        sa.Column("user_agent", sa.Text(), nullable=True),
+        sa.Column("metadata", sa.JSON(), nullable=True),
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["ab_user.id"],
+            name="fk_auth_audit_log_user_id_ab_user",
+        ),

Review Comment:
   **Suggestion:** The foreign key to `ab_user.id` has no `ondelete` behavior, so deleting a user with audit rows will fail due to referential integrity errors. Because `user_id` is nullable and audit rows should typically survive user deletion, configure the FK with `ondelete="SET NULL"` (or explicitly cascade if that is the intended retention policy). [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Deleting users with audit rows raises integrity errors.
   - ⚠️ User administration delete operations can unexpectedly fail.
   - ⚠️ May require manual cleanup of auth_audit_log rows.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Apply migration `ce6bd21901ab` then `b7e4f2a891c3_add_auth_audit_log_table` from
   `superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:34-50`,
   which creates `auth_audit_log` with `user_id` and a foreign key
   `sa.ForeignKeyConstraint(["user_id"], ["ab_user.id"],
   name="fk_auth_audit_log_user_id_ab_user")` without `ondelete`.
   
   2. With AUTH_DB enabled, change a user's password via `PUT /api/v1/me/password`,
   implemented by `CurrentUserRestApi.update_my_password` in
   `superset/views/users/api.py:238-259`; on success it calls `AuthAuditLogDAO.create(...,
   user_id=g.user.id, ...)` at `superset/views/users/api.py:320-31`.
   
   3. `AuthAuditLogDAO.create` in `superset/daos/auth_audit_log.py:28-55` persists an
   `AuthAuditLog` row whose `user_id` references `ab_user.id`, and the ORM model
   `AuthAuditLog` in `superset/models/auth_audit_log.py:35-51` maps `user_id =
   Column(Integer, ForeignKey("ab_user.id"), nullable=True)` to the same constraint, so the
   database now contains audit rows tied to that user.
   
   4. An administrator deletes this user through the standard user API `UserRestApi` in
   `superset/views/users/api.py:415-423` (Flask-AppBuilder's ModelRestApi exposes DELETE
   `/api/v1/user/<id>` based on `resource_name = "user"`); when SQLAlchemy issues `DELETE`
   for the `ab_user` row, the database enforces the `auth_audit_log.user_id -> ab_user.id`
   foreign key without `ondelete`, raising a referential integrity error and causing the
   delete endpoint to fail instead of removing the user or nulling `user_id` in audit rows.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b505859c1a004fc19206a382b748b49d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b505859c1a004fc19206a382b748b49d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
   **Line:** 44:48
   **Comment:**
   	*Logic Error: The foreign key to `ab_user.id` has no `ondelete` behavior, so deleting a user with audit rows will fail due to referential integrity errors. Because `user_id` is nullable and audit rows should typically survive user deletion, configure the FK with `ondelete="SET NULL"` (or explicitly cascade if that is the intended retention policy).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=2262f9c0499a73821b4ebed407dafb74bcf59faf4d79a7385536a59e5f561046&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=2262f9c0499a73821b4ebed407dafb74bcf59faf4d79a7385536a59e5f561046&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:
##########
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""merging two heads
+
+Revision ID: 33a0aac0a26a
+Revises: ('c6219cac9270', '33d7e0e21daa')
+Create Date: 2026-06-16 16:00:00.000000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "33a0aac0a26a"
+down_revision = ("c6219cac9270", "33d7e0e21daa")

Review Comment:
   **Suggestion:** The merge revision is wired to `33d7e0e21daa`, which is not the active head of that branch anymore (there are newer descendants). This leaves multiple migration heads after applying this revision, and `alembic upgrade head` can fail with a multiple-heads error. Update `down_revision` to merge `c6219cac9270` with the current head of the other branch. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Alembic upgrade head fails due to multiple migration heads.
   - ❌ Superset schema upgrade pipeline is blocked for affected installs.
   - ⚠️ Operators must manually adjust migrations to proceed.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Inspect the migration graph around `ce6bd21901ab` using the files in
   `superset/migrations/versions`:
   `2025-11-04_11-26_33d7e0e21daa_add_semantic_layers_and_views.py` declares `revision =
   "33d7e0e21daa", down_revision = "ce6bd21901ab"` at lines 7-8;
   `2026-04-22_19-00_31dae2559c05_replay_user_favorite_tag_migration.py` declares `revision =
   "31dae2559c05", down_revision = "33d7e0e21daa"` at lines 11-12;
   `2026-06-01_00-10_c8d2e3f4a5b6_add_guest_token_revoked_before.py` and
   `2026-06-02_10-00_f7a1c93e0b21_add_sessions_invalidated_at.py` both have `down_revision =
   "31dae2559c05"` (see `f7a1c93e0b21_add_sessions_invalidated_at.py:5-7`), and
   `2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py:30-31` merges those two heads
   with `down_revision = ("c8d2e3f4a5b6", "f7a1c93e0b21")`, followed by
   `2026-06-03_10-00_78a40c08b4be_add_server_host_key_to_ssh_tunnels.py:3-4` which sets
   `revision = "78a40c08b4be", down_revision = "b7c9d1e2f3a4"`. This means the
   semantic-layers branch's current head is `78a40c08b4be`, not `33d7e0e21daa`.
   
   2. In parallel, a second branch from `ce6bd21901ab` goes through the new auth audit log:
   `2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:30-31` defines `revision =
   "b7e4f2a891c3", down_revision = "ce6bd21901ab"`, and
   `2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:30-31` sets `revision
   = "c6219cac9270", down_revision = "b7e4f2a891c3"`, so before adding the new merge there
   are two Alembic heads: `78a40c08b4be` (semantic branch) and `c6219cac9270` (auth-session
   branch).
   
   3. The new merge migration `2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py` at
   `superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:26-27`
   declares `revision = "33a0aac0a26a"` and `down_revision = ("c6219cac9270",
   "33d7e0e21daa")`, incorrectly referencing `33d7e0e21daa`, which is an internal ancestor of
   the semantic branch rather than its current head `78a40c08b4be`.
   
   4. On a database that has been upgraded to both heads `c6219cac9270` and `78a40c08b4be`,
   running the standard migration command (e.g. `superset db upgrade`, which uses Alembic
   `upgrade head`) with this PR causes Alembic to apply `33a0aac0a26a` only on top of
   `c6219cac9270` and `33d7e0e21daa`; the semantic branch continues past `33d7e0e21daa` to
   `31dae2559c05`, `b7c9d1e2f3a4`, and `78a40c08b4be`, so Alembic still sees two leaf
   revisions: `78a40c08b4be` and `33a0aac0a26a`. As a result, `alembic heads` shows multiple
   heads and `alembic upgrade head` (or `superset db upgrade`) fails with a multiple-heads
   CommandError instead of collapsing the branches into a single head.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b9feea289dc64cefa6c8f24af66ed6a1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b9feea289dc64cefa6c8f24af66ed6a1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py
   **Line:** 27:27
   **Comment:**
   	*Api Mismatch: The merge revision is wired to `33d7e0e21daa`, which is not the active head of that branch anymore (there are newer descendants). This leaves multiple migration heads after applying this revision, and `alembic upgrade head` can fail with a multiple-heads error. Update `down_revision` to merge `c6219cac9270` with the current head of the other branch.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=75b2bd4edd53977734ec61a29d3b3fd6ba6c2318babc5f733e6755bc3d37933c&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=75b2bd4edd53977734ec61a29d3b3fd6ba6c2318babc5f733e6755bc3d37933c&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3427047597


##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -36,10 +50,33 @@ function UserInfoModal({
   user,
 }: UserInfoModalProps) {
   const { addDangerToast, addSuccessToast } = useToasts();
+  const [passwordPolicy, setPasswordPolicy] = useState<AuthDbPasswordPolicy>(
+    AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  );
+
+  useEffect(() => {
+    if (!show || isEditMode) {
+      return;
+    }
+    SupersetClient.get({
+      endpoint: '/api/v1/me/password/policy',
+    })
+      .then(({ json }) => {
+        if (json?.result) {
+          setPasswordPolicy(json.result as AuthDbPasswordPolicy);
+        }
+      })
+      .catch(() => {
+        // Keep default policy when endpoint is unavailable.
+      });
+  }, [show, isEditMode]);

Review Comment:
   **Suggestion:** The async password-policy fetch in `useEffect` has no cleanup/guard, so if the modal closes before the request resolves, `setPasswordPolicy` can run on an unmounted component (and older in-flight responses can overwrite newer state). Add a mounted/abort guard in the effect cleanup and ignore late responses. [stale reference]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ User profile password-change modal may update state post-unmount.
   - ⚠️ Dev console emits warnings under slow password-policy responses.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the user profile UI implemented in `src/pages/UserInfo/index.tsx`, which imports
   `ChangePasswordModal` at line 33 and conditionally renders it when
   `modalState.resetPassword` is true at lines 245-249 (`<ChangePasswordModal ...
   show={modalState.resetPassword} />`).
   
   2. Trigger the reset-password modal (e.g., by calling the
   `openModal(ModalType.ResetPassword)` logic wired into `modalState` in
   `src/pages/UserInfo/index.tsx`), causing `ChangePasswordModal` to render and pass
   `show={true}` into `UserInfoModal` (aliased as `ChangePasswordModal`) at
   `src/features/userInfo/UserInfoModal.tsx:45-52` and `250-252`.
   
   3. With `show` true and `isEditMode` false, the `useEffect` at
   `src/features/userInfo/UserInfoModal.tsx:57-72` runs, issuing `SupersetClient.get({
   endpoint: '/api/v1/me/password/policy' })` to fetch the password policy and, on success,
   calling `setPasswordPolicy(json.result as AuthDbPasswordPolicy)` in the `.then` handler at
   lines 64-67.
   
   4. While that request is still in flight, close the modal via the
   `closeModal(ModalType.ResetPassword)` path in `src/pages/UserInfo/index.tsx:27-32`, which
   flips `modalState.resetPassword` to false, unmounting `<ChangePasswordModal>` entirely;
   when the pending `SupersetClient.get` resolves afterward, the `.then` callback still
   invokes `setPasswordPolicy` on this now-unmounted `UserInfoModal` instance, leading to a
   state update on an unmounted component (and associated React dev warnings / wasted work)
   because there is no cleanup or mounted-guard in the `useEffect`.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=0eb26e534a5842deabb8c2bc980661fd&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=0eb26e534a5842deabb8c2bc980661fd&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 57:72
   **Comment:**
   	*Stale Reference: The async password-policy fetch in `useEffect` has no cleanup/guard, so if the modal closes before the request resolves, `setPasswordPolicy` can run on an unmounted component (and older in-flight responses can overwrite newer state). Add a mounted/abort guard in the effect cleanup and ignore late responses.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ed36c4475634c3fae3caffa551a2a7e7cb81451fd33e5680e928744e72638e77&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ed36c4475634c3fae3caffa551a2a7e7cb81451fd33e5680e928744e72638e77&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3432570597


##########
superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:
##########
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add auth_audit_log table
+
+Revision ID: b7e4f2a891c3
+Revises: 78a40c08b4be
+Create Date: 2026-04-19 14:30:00.000000
+
+"""
+
+import uuid
+
+import sqlalchemy as sa
+from sqlalchemy_utils import UUIDType
+
+from superset.migrations.shared.utils import create_table, drop_table
+
+# revision identifiers, used by Alembic.
+revision = "b7e4f2a891c3"
+down_revision = "78a40c08b4be"
+
+
+def upgrade() -> None:
+    """Create the append-only authentication audit log table."""
+    create_table(
+        "auth_audit_log",
+        sa.Column(
+            "id",
+            UUIDType(binary=True),
+            primary_key=True,
+            default=uuid.uuid4,
+            nullable=False,
+        ),
+        sa.Column("user_id", sa.Integer(), nullable=True),
+        sa.Column("event_type", sa.String(length=64), nullable=False),
+        sa.Column("ip_address", sa.String(length=256), nullable=True),
+        sa.Column("user_agent", sa.Text(), nullable=True),
+        sa.Column("metadata", sa.JSON(), nullable=True),
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["ab_user.id"],
+            name="fk_auth_audit_log_user_id_ab_user",
+            ondelete="SET NULL",
+        ),

Review Comment:
   **Suggestion:** Replace the integer-based user reference with a UUID-based user identifier and corresponding UUID foreign key target to avoid introducing internal integer IDs in this new UUID-keyed table. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This new table uses a UUID primary key for `id`, but it still introduces `user_id` as an integer foreign key to `ab_user.id`, which exposes an internal integer identifier in a UUID-keyed schema. That matches the custom rule against adding internal integer IDs when UUID identifiers are being added or changed.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=62d9b1d0d4d34920968a232573072fc0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=62d9b1d0d4d34920968a232573072fc0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
   **Line:** 48:59
   **Comment:**
   	*Custom Rule: Replace the integer-based user reference with a UUID-based user identifier and corresponding UUID foreign key target to avoid introducing internal integer IDs in this new UUID-keyed table.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=e40a13ee88a64afb849de6b0c4a0e1ced3c71b9ba6e0e509b8f243f197e18476&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=e40a13ee88a64afb849de6b0c4a0e1ced3c71b9ba6e0e509b8f243f197e18476&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3432556583


##########
superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:
##########
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""merging two heads
+
+Revision ID: 33a0aac0a26a
+Revises: ('c6219cac9270', '78a40c08b4be')

Review Comment:
   I will revert it. It was causing issue when I did rebase from main.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3460157571


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,198 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS = frozenset({"bcrypt", "argon2"})
+
+_COMMON_PASSWORDS = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides = app.config.get("AUTH_DB_CONFIG") or {}
+    return {**AUTH_DB_DEFAULTS, **overrides}

Review Comment:
   **Suggestion:** `AUTH_DB_CONFIG` is assumed to be a mapping, but invalid config types (for example a string/list from `superset_config`) will raise `TypeError` when unpacked, causing password-related requests to fail with 500. Validate that overrides are a dict-like mapping before merging and raise a controlled validation error when misconfigured. [type error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ `/api/v1/me/password/policy` fails with HTTP 500.
   - ❌ Password change rate limiting crashes when limiter enabled.
   - ⚠️ AUTH_DB password features brittle to config type mistakes.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset for database auth by setting `AUTH_TYPE = AUTH_DB` so that the
   password endpoints in `CurrentUserRestApi` (superset/views/users/api.py:295-385) are
   reachable.
   
   2. Misconfigure `AUTH_DB_CONFIG` in the Superset configuration so that
   `app.config["AUTH_DB_CONFIG"]` is a non-mapping value (for example a string), which is
   read in `get_merged_auth_db_config()` at `superset/utils/auth_db_password.py:100-103`.
   
   3. From an authenticated session, call `GET /api/v1/me/password/policy`, which is
   implemented by `CurrentUserRestApi.get_my_password_policy()` at
   `superset/views/users/api.py:386-419` and calls `get_public_auth_db_password_policy()`
   imported from `superset/utils/auth_db_password.py:37-41`.
   
   4. `get_public_auth_db_password_policy()` calls `get_merged_auth_db_config()`
   (superset/utils/auth_db_password.py:100-103), where `return {**AUTH_DB_DEFAULTS,
   **overrides}` at line 103 attempts to unpack the non-mapping `overrides` and raises
   `TypeError: type object argument after ** must be a mapping, not <type>`, causing Flask to
   return HTTP 500 instead of a controlled configuration error for all password-policy and
   rate-limited password change flows.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=548d5863e40b4251a8066335cad82873&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=548d5863e40b4251a8066335cad82873&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 102:103
   **Comment:**
   	*Type Error: `AUTH_DB_CONFIG` is assumed to be a mapping, but invalid config types (for example a string/list from `superset_config`) will raise `TypeError` when unpacked, causing password-related requests to fail with 500. Validate that overrides are a dict-like mapping before merging and raise a controlled validation error when misconfigured.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5fa624f8e11370ea11e2c4e6204031982266e0f106ccdb3869fb2d6b472a815f&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5fa624f8e11370ea11e2c4e6204031982266e0f106ccdb3869fb2d6b472a815f&reaction=dislike'>👎</a>



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,154 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register a before_request handler that enforces the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    stamp = ensure_user_session_stamp_value(int(uid))
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> None:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if not has_request_context():
+        return
+    if not getattr(current_user, "is_authenticated", False):
+        return
+    if getattr(current_user, "is_guest_user", False):
+        return
+    raw_id = current_user.get_id()
+    if raw_id is None:
+        return
+    try:
+        user_id = int(raw_id)
+    except (TypeError, ValueError):
+        return
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        stamp = ensure_user_session_stamp_value(user_id)
+        session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+        return

Review Comment:
   **Suggestion:** The request hook performs database reads/writes without any exception handling, so DB-level failures (including a missing table during rolling deploy/migration mismatch) will raise out of `before_request` and turn authenticated requests into 500 errors. Wrap this check in a fail-open/fallback path (log and continue or safely log out) to avoid global request failures. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ All authenticated requests 500 during schema mismatch.
   - ⚠️ Rolling upgrades brittle to migration timing issues.
   - ⚠️ Session-stamp failures block otherwise healthy endpoints.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Deploy the PR code to an environment where the `user_session_auth_stamp` table has not
   yet been created (for example, migration `add_user_session_auth_stamp_table` has not run),
   so the ORM model `UserSessionAuthStamp` defined at
   `superset/models/user_session_auth_stamp.py:28-39` points to a missing table while other
   user tables exist.
   
   2. Start Superset and create a login manager via
   `SupersetSecurityManager.create_login_manager()` at `superset/security/manager.py:19-25`,
   which imports `register_session_auth_stamp_hook` and registers the
   `_validate_user_session_auth_stamp` `before_request` hook at
   `superset/utils/auth_session_stamp.py:37-46`.
   
   3. Log in as any user so that `current_user.is_authenticated` is `True` on subsequent
   requests, then invoke a simple authenticated endpoint such as `GET /api/v1/me/`
   implemented by `CurrentUserRestApi.get_me()` at `superset/views/users/api.py:175-201`.
   
   4. For each authenticated request, the `before_request` handler calls
   `validate_session_auth_stamp_for_request()`
   (superset/utils/auth_session_stamp.py:123-153), which executes `row =
   db.session.get(UserSessionAuthStamp, user_id)` at line 141 while the underlying table is
   missing; SQLAlchemy raises an OperationalError/ProgrammingError, this exception is not
   caught anywhere in the hook or view, and Flask converts it into an HTTP 500 response for
   every authenticated request until the migration is applied or the hook is disabled.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9293901280a941fd8adc34341be8964f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9293901280a941fd8adc34341be8964f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 141:145
   **Comment:**
   	*Possible Bug: The request hook performs database reads/writes without any exception handling, so DB-level failures (including a missing table during rolling deploy/migration mismatch) will raise out of `before_request` and turn authenticated requests into 500 errors. Wrap this check in a fail-open/fallback path (log and continue or safely log out) to avoid global request failures.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7d14b0a2cb78b08f8f30c11c6db0565e55a7616f1f2673e442fa3193e46fddfd&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=7d14b0a2cb78b08f8f30c11c6db0565e55a7616f1f2673e442fa3193e46fddfd&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,198 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS = frozenset({"bcrypt", "argon2"})
+
+_COMMON_PASSWORDS = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides = app.config.get("AUTH_DB_CONFIG") or {}
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    """
+    return str(
+        get_merged_auth_db_config().get(
+            "login_rate_limit", AUTH_DB_DEFAULTS["login_rate_limit"]
+        )
+    )
+
+
+def get_public_auth_db_password_policy(cfg: dict[str, Any] | None = None) -> dict[str, Any]:
+    """
+    Return non-secret AUTH_DB password policy options for frontend validation UI.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    return {
+        key: merged_cfg.get(key, AUTH_DB_DEFAULTS[key])
+        for key in _PUBLIC_PASSWORD_POLICY_KEYS
+    }
+
+
+def get_auth_db_password_hash_algorithm(cfg: dict[str, Any] | None = None) -> str:
+    """
+    Return the password hash algorithm for AUTH_DB.
+
+    ``AUTH_DB_CONFIG["password_hash_algorithm"]`` accepts ``bcrypt`` (default)
+    or ``argon2``.
+    """
+    merged_cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    raw_algorithm = merged_cfg.get(
+        "password_hash_algorithm", AUTH_DB_DEFAULTS["password_hash_algorithm"]
+    )
+    algorithm = str(raw_algorithm).strip().lower()
+    if algorithm not in _SUPPORTED_HASH_ALGORITHMS:
+        supported = ", ".join(sorted(_SUPPORTED_HASH_ALGORITHMS))
+        raise ValidationError(
+            {
+                "password_hash_algorithm": [
+                    "Invalid AUTH_DB_CONFIG.password_hash_algorithm. "
+                    f"Expected one of: {supported}."
+                ]
+            }
+        )
+    return algorithm
+
+
+def get_auth_db_password_hash_method(cfg: dict[str, Any] | None = None) -> str:
+    """Backward-compatible alias for :func:`get_auth_db_password_hash_algorithm`."""
+    return get_auth_db_password_hash_algorithm(cfg)
+
+
+def validate_auth_db_password(password: str, cfg: dict[str, Any] | None = None) -> None:
+    """
+    Enforce AUTH_DB password policy.
+
+    :param password: Candidate password (plaintext).
+    :param cfg: Optional merged config; loaded from the current app when omitted.
+    :raises marshmallow.ValidationError: When validation fails.
+    """
+    cfg = cfg if cfg is not None else get_merged_auth_db_config()
+    errors: list[str] = []
+
+    min_len = int(cfg.get("password_min_length", AUTH_DB_DEFAULTS["password_min_length"]))

Review Comment:
   **Suggestion:** Converting `password_min_length` with `int(...)` can throw `ValueError`/`TypeError` for invalid config values, which currently bubbles up as a server error instead of a user-facing validation response. Normalize and validate this config value explicitly before using it in request-time password checks. [type error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ `/api/v1/me/password` returns 500 for bad policy config.
   - ⚠️ Users cannot change passwords until config is fixed.
   - ⚠️ Error messaging hides root cause from administrators.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset for database auth by setting `AUTH_TYPE = AUTH_DB` so that `PUT
   /api/v1/me/password` is enabled via `CurrentUserRestApi.update_my_password()` in
   `superset/views/users/api.py:295-385`.
   
   2. Set `AUTH_DB_CONFIG` in the Superset configuration to a dict with a non-numeric
   `password_min_length`, for example `{"password_min_length": "twelve"}`, so that
   `cfg["password_min_length"]` is a string when read in `validate_auth_db_password()` at
   `superset/utils/auth_db_password.py:168-172`.
   
   3. As an authenticated user, call `PUT /api/v1/me/password` with a JSON body that passes
   schema validation (parsed by `CurrentUserPasswordPutSchema` and
   `_load_password_change_body()` at `superset/views/users/api.py:105-112`) and includes a
   `new_password` field.
   
   4. `_load_password_change_body()` calls `validate_auth_db_password(body["new_password"])`
   (superset/views/users/api.py:109-112), which executes `min_len =
   int(cfg.get("password_min_length", AUTH_DB_DEFAULTS["password_min_length"]))` at
   `superset/utils/auth_db_password.py:171`; `int("twelve")` raises `ValueError`, which is
   not caught by `update_my_password()` (it only catches `marshmallow.ValidationError` at
   lines 359-360), so the request surfaces as an unhandled exception and returns HTTP 500
   instead of a structured validation error.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ac696731b33b4f33b6cab08ab52eaf23&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ac696731b33b4f33b6cab08ab52eaf23&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 171:171
   **Comment:**
   	*Type Error: Converting `password_min_length` with `int(...)` can throw `ValueError`/`TypeError` for invalid config values, which currently bubbles up as a server error instead of a user-facing validation response. Normalize and validate this config value explicitly before using it in request-time password checks.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=079ed4b1082726480440de557f73ee95ccaafa2d2062479a40e7e99606074c23&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=079ed4b1082726480440de557f73ee95ccaafa2d2062479a40e7e99606074c23&reaction=dislike'>👎</a>



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,154 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register a before_request handler that enforces the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    stamp = ensure_user_session_stamp_value(int(uid))
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> None:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if not has_request_context():
+        return
+    if not getattr(current_user, "is_authenticated", False):
+        return
+    if getattr(current_user, "is_guest_user", False):
+        return
+    raw_id = current_user.get_id()
+    if raw_id is None:
+        return
+    try:
+        user_id = int(raw_id)
+    except (TypeError, ValueError):
+        return
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        stamp = ensure_user_session_stamp_value(user_id)
+        session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+        return
+
+    sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)
+    if sess_stamp is None:
+        session[SESSION_AUTH_STAMP_SESSION_KEY] = row.stamp
+        return

Review Comment:
   **Suggestion:** Treating a missing session stamp as valid allows pre-existing sessions to survive a password change. If another browser session has no `_auth_session_stamp` yet, this branch silently seeds it with the latest DB stamp instead of invalidating the session, so the "invalidate all sessions on password change" guarantee is bypassed. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Pre-existing sessions survive a user password change.
   - ❌ Password changes fail to revoke all active logins.
   - ⚠️ Increases risk of unauthorized access via stale browsers.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Before deploying this PR, have a user log in to Superset so that Flask-Login creates a
   browser session for that user without any `_auth_session_stamp` key in the session; the
   current code later reads this key via `session[SESSION_AUTH_STAMP_SESSION_KEY]` inside
   `validate_session_auth_stamp_for_request()` in
   `superset/utils/auth_session_stamp.py:147-150`.
   
   2. Deploy the PR version (which registers `register_session_auth_stamp_hook()` in the
   login manager at `superset/security/manager.py:19-25` and defines the
   `UserSessionAuthStamp` model at `superset/models/user_session_auth_stamp.py:28-39`), then
   log in as the same user in a second browser and change the password through `PUT
   /api/v1/me/password` handled by `CurrentUserRestApi.update_my_password()` at
   `superset/views/users/api.py:295-385`.
   
   3. The password change calls `_commit_user_password_change()`
   (superset/views/users/api.py:115-145), which invokes
   `bump_user_session_auth_stamp(user_id)` at line 139, updating `UserSessionAuthStamp.stamp`
   for that user in the database via the logic in
   `superset/utils/auth_session_stamp.py:100-120`, and then `_reestablish_login_session()`
   logs the user back in with the new stamp.
   
   4. Returning to the original pre-upgrade browser session, make any authenticated request
   (for example `GET /api/v1/me/` at `superset/views/users/api.py:175-201`), which triggers
   the `_validate_user_session_auth_stamp` before-request hook at
   `superset/utils/auth_session_stamp.py:43-46`; inside
   `validate_session_auth_stamp_for_request()` (lines 123-153), `row =
   db.session.get(UserSessionAuthStamp, user_id)` at line 141 finds the updated row, but
   `sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)` at line 147 returns `None` for
   this legacy session, so the branch at lines 148-150 simply seeds
   `session[SESSION_AUTH_STAMP_SESSION_KEY] = row.stamp` and returns without calling
   `logout_user()`, leaving this pre-existing session fully authenticated even though the
   password was changed, contrary to the "invalidate all browser sessions when a user's
   password changes" guarantee documented in the module docstring at
   `superset/utils/auth_session_stamp.py:17` and in `UserSessionAuthStamp`'s comment at
   `superset/models/user_session_auth_stamp.py:30-33`.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=605dfdde664746688af638e7c9016ef8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=605dfdde664746688af638e7c9016ef8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 147:150
   **Comment:**
   	*Security: Treating a missing session stamp as valid allows pre-existing sessions to survive a password change. If another browser session has no `_auth_session_stamp` yet, this branch silently seeds it with the latest DB stamp instead of invalidating the session, so the "invalidate all sessions on password change" guarantee is bypassed.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=cd499e1a4a94c3e12a69ffc1b01573b64ed6447c32edb99cea13bc3b639cccce&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=cd499e1a4a94c3e12a69ffc1b01573b64ed6447c32edb99cea13bc3b639cccce&reaction=dislike'>👎</a>



##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,274 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+/** Mirrors ``int(cfg.get("password_min_length", default))`` in auth_db_password.py. */
+function resolveAuthDbPasswordMinLength(
+  passwordMinLength: number | undefined | null,
+): number {
+  if (passwordMinLength === undefined || passwordMinLength === null) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  const parsed = Number(passwordMinLength);
+  if (!Number.isFinite(parsed)) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  return Math.trunc(parsed);
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    crypto.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(
+  policy: AuthDbPasswordPolicy,
+): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+function satisfiesAuthDbPasswordPolicy(
+  password: string,
+  policy: AuthDbPasswordPolicy,
+): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  return Object.values(checks).every(Boolean);
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(password: string): boolean {
+  return satisfiesAuthDbPasswordPolicy(password, AUTH_DB_DEFAULT_PASSWORD_POLICY);
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\d/.test(password),

Review Comment:
   **Suggestion:** This digit check is narrower than backend validation: JavaScript `\d` matches only ASCII digits, while backend Python `\d` accepts Unicode decimal digits. That mismatch causes frontend-side false rejections for passwords the API would accept, breaking client/server policy consistency. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password reset UI rejects backend-acceptable Unicode-digit passwords.
   - ⚠️ Breaks documented parity between frontend and backend password policy.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the Change Password modal implemented in
   `superset-frontend/src/features/userInfo/UserInfoModal.tsx` lines 86-177; the
   `ResetPasswordFields` component renders a `new_password` `<FormItem>` with a custom
   validator at lines 100-115.
   
   2. When the user types a new password, the validator at `UserInfoModal.tsx:100-115` calls
   `getPasswordPolicyError(password)`, which forwards to `getAuthDbPasswordPolicyError` in
   `superset-frontend/src/utils/generateAuthDbPassword.ts:219-249` using the current AUTH_DB
   password policy loaded from `/api/v1/me/password/policy` (lines 3-9).
   
   3. `getAuthDbPasswordPolicyError` calls `getAuthDbPasswordPolicyChecks` at
   `generateAuthDbPassword.ts:200-216`, where the `digit` check is implemented as `digit:
   !policy.password_require_digit || /\d/.test(password)` on line 209; enter a password
   containing only Unicode decimal digits (for example `'١٢٣٤AB!'` using Arabic-Indic digits)
   so that it contains no ASCII `0-9`.
   
   4. On the backend, the same password would be validated by `validate_auth_db_password` in
   `superset/utils/auth_db_password.py:160-198`, whose digit rule uses `re.search(r"\d",
   password)` at line 181; Python `\d` matches Unicode decimal digits, so the backend
   considers the digit requirement satisfied, while the frontend `/\d/` check at line 209
   fails and returns the message "Password must contain at least one digit.", blocking form
   submission and demonstrating a concrete client/server policy mismatch.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=0939681d48e641af98285fc12a13dbb1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=0939681d48e641af98285fc12a13dbb1&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 209:209
   **Comment:**
   	*Api Mismatch: This digit check is narrower than backend validation: JavaScript `\d` matches only ASCII digits, while backend Python `\d` accepts Unicode decimal digits. That mismatch causes frontend-side false rejections for passwords the API would accept, breaking client/server policy consistency.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=450bb9eeef079f6222c02888df6b9d0ae6e87b10bddd8512597d33ea49d754a6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=450bb9eeef079f6222c02888df6b9d0ae6e87b10bddd8512597d33ea49d754a6&reaction=dislike'>👎</a>



##########
superset/cli/reset.py:
##########
@@ -68,7 +68,7 @@ def factory_reset(
     # Validate the user
     password = click.prompt("Admin Password", hide_input=True)
     user = security_manager.find_user(username)
-    if not user or not check_password_hash(user.password, password):
+    if not user or not verify_auth_db_password(user.password, password):
         click.secho("Invalid credentials", fg="red")
         sys.exit(1)

Review Comment:
   **Suggestion:** This credential check bypasses account state validation by only verifying the password hash. A deactivated admin account can still pass this check and run a full factory reset if it has the `Admin` role. Reuse the standard DB auth flow (or explicitly require `user.is_active`) before allowing reset. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Inactive admin can run destructive factory reset via CLI.
   - ⚠️ Bypasses standard AUTH_DB account-state checks enforced at login.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Enable the factory reset CLI by setting the `ENABLE_FACTORY_RESET_COMMAND` feature flag
   so that the guard at `superset/cli/reset.py:53-56` allows `factory_reset` to run, and
   configure Superset to use AUTH_DB authentication.
   
   2. Create an admin user with the `Admin` role, then deactivate the account (set
   `user.is_active = False`) via normal administration; note that the standard AUTH_DB login
   path `auth_user_db` in `superset/security/manager.py:7-35` explicitly rejects users where
   `user is None or (not user.is_active)` (line 20).
   
   3. On the Superset host, invoke the CLI command `factory_reset` defined in
   `superset/cli/reset.py:26-80`, enter the deactivated admin's username when prompted at the
   `@click.option("--username", prompt="Admin Username")` (line 28), and enter their known
   password at `click.prompt("Admin Password", hide_input=True)` on line 69.
   
   4. The command looks up the user via `security_manager.find_user(username)` (line 70) and
   then performs only a password check using `verify_auth_db_password(user.password,
   password)` at line 71; if this returns True and the user still has an `Admin` role
   (checked at line 74), `ResetSupersetCommand(silent, user, exclude_users,
   exclude_roles).run()` executes at lines 78-79, allowing a deactivated admin account to
   perform a full factory reset despite the normal login flow blocking `is_active=False`
   users.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=0e8bb549dff74c819d1ca703ce3d8feb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=0e8bb549dff74c819d1ca703ce3d8feb&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/cli/reset.py
   **Line:** 71:73
   **Comment:**
   	*Security: This credential check bypasses account state validation by only verifying the password hash. A deactivated admin account can still pass this check and run a full factory reset if it has the `Admin` role. Reuse the standard DB auth flow (or explicitly require `user.is_active`) before allowing reset.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=16327d0d958d378ea9e59458723a8dea85ed10983ad32ecc54b46b0e92f0234d&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=16327d0d958d378ea9e59458723a8dea85ed10983ad32ecc54b46b0e92f0234d&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4797705335

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4843219657

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3497233595


##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  css,
+  styled,
+  useTheme,
+  type SupersetTheme,
+} from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import {
+  Icons,
+  Popover,
+  Progress,
+  Typography,
+} from '@superset-ui/core/components';
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`
+    flex: 1;
+    cursor: help;
+
+    .ant-progress {
+      margin-bottom: 0;
+    }
+  `}
+`;
+
+const Checklist = styled.div`
+  ${({ theme }) => css`
+    min-width: ${theme.sizeUnit * 70}px;
+    display: flex;
+    flex-direction: column;
+    gap: ${theme.sizeUnit}px;
+  `}
+`;
+
+const ChecklistItem = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+    font-size: ${theme.fontSize}px;
+  `}
+`;
+
+const requirementText = {
+  minLength: (minLength: number) => t('At least %s characters', minLength),
+  uppercase: t('Contains an uppercase letter'),
+  lowercase: t('Contains a lowercase letter'),
+  digit: t('Contains a digit'),
+  special: t('Contains a special character'),
+  commonPassword: t('Is not a common password'),
+};
+
+function getStrengthState(percent: number, theme: SupersetTheme) {
+  if (percent <= 33) {
+    return { color: theme.colorError, label: t('Very weak') };
+  }
+  if (percent <= 50) {
+    return { color: theme.colorWarningText, label: t('Weak') };
+  }
+  if (percent <= 67) {
+    return { color: theme.colorWarning, label: t('Medium') };
+  }
+  if (percent <= 83) {
+    return { color: theme.colorSuccess, label: t('Strong') };
+  }
+  return { color: theme.colorSuccessTextActive, label: t('Very strong') };
+}
+
+export default function AuthDbPasswordPolicyIndicator({
+  password,
+  policy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+}: AuthDbPasswordPolicyIndicatorProps) {
+  const theme = useTheme();
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  const hasPassword = password.length > 0;
+  const checklist = [
+    {
+      label: requirementText.minLength(policy.password_min_length),
+      passed: hasPassword && checks.minLength,
+    },
+    ...(policy.password_require_uppercase
+      ? [
+          {
+            label: requirementText.uppercase,
+            passed: hasPassword && checks.uppercase,
+          },
+        ]
+      : []),
+    ...(policy.password_require_lowercase
+      ? [
+          {
+            label: requirementText.lowercase,
+            passed: hasPassword && checks.lowercase,
+          },
+        ]
+      : []),
+    ...(policy.password_require_digit
+      ? [{ label: requirementText.digit, passed: hasPassword && checks.digit }]
+      : []),
+    ...(policy.password_require_special
+      ? [
+          {
+            label: requirementText.special,
+            passed: hasPassword && checks.special,
+          },
+        ]
+      : []),
+    ...(policy.password_common_list_check
+      ? [
+          {
+            label: requirementText.commonPassword,
+            passed: hasPassword && checks.commonPassword,
+          },
+        ]
+      : []),
+  ];
+  const passedChecks = checklist.filter(item => item.passed).length;
+  const percent = Math.round((passedChecks / checklist.length) * 100);
+  const strength = getStrengthState(percent, theme);
+
+  return (
+    <StrengthWrapper>
+      <Popover
+        trigger="hover"
+        placement="topLeft"
+        content={
+          <Checklist>
+            {checklist.map(item => (
+              <ChecklistItem key={item.label}>
+                {item.passed ? (
+                  <Icons.CheckCircleFilled iconColor="success" iconSize="m" />
+                ) : (
+                  <Icons.CloseCircleOutlined iconColor="danger" iconSize="m" />
+                )}
+                <Typography.Text>{item.label}</Typography.Text>
+              </ChecklistItem>
+            ))}
+          </Checklist>
+        }
+      >
+        <StrengthBarContainer>
+          <Progress
+            percent={percent}
+            showInfo={false}
+            strokeColor={strength.color}
+            size="small"
+          />
+        </StrengthBarContainer>
+      </Popover>
+      <Typography.Text type="secondary">{strength.label}</Typography.Text>
+    </StrengthWrapper>
+  );
+}

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing unit tests for new component</b></div>
   <div id="fix">
   
   New component with business logic (strength calculation at lines 153-155, conditional checklist generation at lines 112-152) lacks unit test coverage. BITO.md rule [11730] requires comprehensive tests for new tools covering success paths, error scenarios, and edge cases. A component that visualizes password strength and validates policy requirements needs tests to verify correctness of the percentage and label logic.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #27f12a</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,288 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/**
+ * Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in
+ * ``superset/utils/auth_db_password.py``. Sync is enforced by
+ * ``tests/unit_tests/utils/test_auth_db_password.py``.
+ */
+export const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+/** Mirrors ``int(cfg.get("password_min_length", default))`` in auth_db_password.py. */
+function resolveAuthDbPasswordMinLength(
+  passwordMinLength: number | undefined | null,
+): number {
+  if (passwordMinLength === undefined || passwordMinLength === null) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  const parsed = Number(passwordMinLength);
+  if (!Number.isFinite(parsed)) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  return Math.trunc(parsed);
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const cryptoObj = globalThis.crypto;
+  if (!cryptoObj?.getRandomValues) {
+    throw new Error(
+      'secureRandomInt: a Web Crypto implementation with getRandomValues ' +
+        'is required to generate passwords',
+    );
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    cryptoObj.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(policy: AuthDbPasswordPolicy): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\p{Nd}/u.test(password),

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Digit-check regex divergence</b></div>
   <div id="fix">
   
   `digit` check uses Unicode-aware `\p{Nd}` (line 205) while backend uses ASCII-only `r"\d"` (`auth_db_password.py:211`). These differ on characters like `০` (Bengali zero). A password containing only non-ASCII digits would pass frontend but fail server validation.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #27f12a</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5019882639

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5025539445

   Did another sweep of the review threads now that CI's green. The latest refactor addressed most of what was open (typed constants, the shared-cache refresh on stamp bumps, the generator error handling, the audit event @EnxDev asked for, and more), so I resolved those to cut the noise.
   
   Two left open on purpose. The lockout/rate-limit keys in `AUTH_DB_CONFIG` still aren't consumed anywhere... the docs now call them reserved, but I'd lean toward dropping them until a pillar actually wires them up, curious what you think @shantanukhond. And the OpenAPI docstring for the password endpoint maps 429 to the 400 response component, which needs a proper 429 component (there isn't one yet).
   
   Thanks for grinding through the fixes, this is looking close!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5025540910

   @dpgaspar this is the one design call I'd really like your eyes on before this lands, since it touches core auth semantics.
   
   The session-stamp check (`superset/utils/auth_session_stamp.py`, `validate_session_auth_stamp_for_request`) fails open in two cases. A logged-in user with no stamp row in the DB is allowed through, since rows are only created at login and this covers sessions that predate the upgrade. And a session cookie carrying no stamp adopts the current DB value when the session is flask-login fresh. Remember-cookie sessions without a stamp are invalidated.
   
   The residual gap: a signed pre-upgrade cookie keeps its fresh flag, so if one had been exfiltrated it would adopt the new stamp and survive the first password change after the upgrade, one time. Is that an acceptable rollout tradeoff to keep from mass-logging-out every session on upgrade, or would you rather we fail closed and force a one-time re-login for everyone?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4855827220

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4767357260

   Working on reverting back all the changes other than `/me/password` focusing only on this part for this PR. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4709962351

   Would love to see this through... if it's really a breaking change, the 7.0 window is about to open. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3601710370


##########
superset/views/users/api.py:
##########
@@ -161,6 +309,135 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies
+            ``AUTH_DB_CONFIG`` policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            429:
+              $ref: '#/components/responses/400'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if app.config.get("AUTH_TYPE") != AUTH_DB:
+            return self.response_400(
+                message=(
+                    "Password change is only available when AUTH_TYPE is AUTH_DB."
+                ),
+            )
+
+        try:
+            body = _load_password_change_body(
+                self.current_user_password_put_schema,
+                request.json,
+            )
+            user_db = db.session.get(User, g.user.id)
+            if user_db is None:
+                return self.response_404()
+
+            old_hash = user_db.password
+            if not verify_auth_db_password(old_hash, body["current_password"]):
+                return self.response_400(message="Incorrect current password.")
+
+            new_hash = hash_auth_db_password(body["new_password"])
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+        try:
+            user_after, new_stamp = _commit_user_password_change(
+                self,
+                g.user.id,
+                old_hash,
+                new_hash,
+            )
+        except PasswordChangeConflictError:
+            return self.response_400(
+                message=(
+                    "Unable to update password. Your password may have been "
+                    "changed elsewhere; please try again."
+                ),
+            )
+        except SQLAlchemyError:
+            db.session.rollback()  # pylint: disable=consider-using-transaction
+            logger.exception("Failed to commit password change")
+            return self.response_500(
+                message="Unable to update password. Please try again.",
+            )
+
+        cache_user_session_auth_stamp(g.user.id, new_stamp)
+        _reestablish_login_session(user_after)
+        _log_audit_event("UserPasswordChanged", {"user_id": g.user.id})

Review Comment:
   Would it make sense to add more details here? 



##########
superset/security/manager.py:
##########
@@ -1334,22 +1349,50 @@ def reset_password(self, userid: Union[int, str], password: str) -> None:
         bypassed. We distinguish the two by comparing the acting user
         (``g.user``) against the target ``userid``: they match for a
         self-service reset and differ for an admin reset.
+
+        On ``AUTH_DB``, the per-user session auth stamp is rotated so every
+        other session for the target account is invalidated — including when an
+        administrator resets a compromised password.
         """
+        # pylint: disable=import-outside-toplevel
+        from flask_appbuilder.const import AUTH_DB
+
         super().reset_password(userid, password)
 
+        try:
+            target_user_id = int(userid)
+        except (TypeError, ValueError):
+            target_user_id = None
+
         acting_user = getattr(g, "user", None)
         acting_user_id = getattr(acting_user, "id", None)
         # ``userid`` arrives as a string (the ``pk`` request arg) on the admin
         # path, so coerce both sides before comparing.
-        is_self_service = acting_user_id is not None and self._same_user(
-            acting_user_id, userid
+        is_self_service = (
+            acting_user_id is not None
+            and target_user_id is not None
+            and self._same_user(acting_user_id, userid)
         )
-        if is_self_service:
+
+        if (
+            current_app.config.get("AUTH_TYPE") == AUTH_DB
+            and target_user_id is not None
+        ):
+            from superset.utils.auth_session_stamp import (
+                bump_user_session_auth_stamp,
+                sync_session_auth_stamp_on_login,
+            )
+
+            bump_user_session_auth_stamp(target_user_id)
+            if is_self_service and acting_user is not None:
+                sync_session_auth_stamp_on_login(acting_user)
+
+        if is_self_service and target_user_id is not None:
             from superset.security.password_change import (
                 clear_password_must_change,
             )
 
-            clear_password_must_change(int(userid))
+            clear_password_must_change(target_user_id)

Review Comment:
   @shantanukhond would it make sense to add an `_log_audit_event` here so we can track who changed the password?
   



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,382 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> str:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return new_stamp
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    return new_stamp
+
+
+def _resolve_stamp_check_user_id() -> int | None:
+    """Return the integer user id for the stamp check, or None to skip it."""
+    from flask import current_app
+    from flask_appbuilder.const import AUTH_DB
+    from sqlalchemy.orm.exc import DetachedInstanceError
+
+    if not has_request_context():
+        return None
+    # The stamp is only ever bumped on AUTH_DB password changes; skip the DB
+    # hit for other auth backends where the stamp never changes after login.
+    if current_app.config.get("AUTH_TYPE") != AUTH_DB:
+        return None
+    if not getattr(current_user, "is_authenticated", False):
+        return None
+    if getattr(current_user, "is_guest_user", False):
+        return None
+    try:
+        raw_id = current_user.get_id()
+    except DetachedInstanceError:
+        # The user object was detached from the session (e.g. after a rollback
+        # in a previous before_request hook). Fail open — Flask-Login will
+        # reload the user on the next request.
+        logger.warning(
+            "Skipping session auth stamp check: current_user is detached",
+            exc_info=True,
+        )
+        return None
+    if raw_id is None:
+        return None
+    try:
+        return int(raw_id)
+    except (TypeError, ValueError):
+        return None
+
+
+def _should_skip_stamp_db_lookup(
+    user_id: int, method: str, sess_stamp: str | None
+) -> bool:
+    """Return True when a recent DB check lets us skip another on read-only traffic."""
+    if method not in _SAFE_METHODS or sess_stamp is None:
+        return False
+
+    revalidation_seconds = _get_revalidation_seconds()
+    if revalidation_seconds <= 0:
+        return False
+
+    validated_at = session.get(SESSION_AUTH_STAMP_VALIDATED_AT_KEY)
+    if validated_at is None:
+        return False
+    try:
+        elapsed = time.time() - float(validated_at)
+    except (TypeError, ValueError):
+        return False
+    if elapsed >= revalidation_seconds:
+        return False
+
+    # Only skip when this session still carries the stamp we last validated and
+    # the shared cache agrees (so password rotation on another client is visible).
+    if session.get(SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY) != sess_stamp:
+        return False
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    return cached_stamp == sess_stamp
+
+
+def _mark_session_stamp_validated(db_stamp: str | None = None) -> None:
+    session[SESSION_AUTH_STAMP_VALIDATED_AT_KEY] = time.time()
+    if db_stamp is not None:
+        session[SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY] = db_stamp
+    session.modified = True
+
+
+def _load_db_stamp_for_user(
+    user_id: int, *, use_cache: bool = True
+) -> tuple[str | None, bool]:
+    """
+    Return ``(stamp, db_error)``.
+
+    ``db_error`` is True when the database lookup failed and the check should
+    fail open. ``stamp`` is None when no row exists.
+    """
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if use_cache:
+        cached_stamp = _get_cached_user_session_stamp(user_id)
+        if cached_stamp is not None:
+            return cached_stamp, False
+
+    try:
+        with db.session.begin_nested():
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            db_stamp = row.stamp if row is not None else None
+    except SQLAlchemyError:
+        logger.warning(
+            "Skipping session auth stamp check due to a database error",
+            exc_info=True,
+        )
+        return None, True
+
+    if db_stamp is not None:
+        _cache_user_session_stamp(user_id, db_stamp)
+    return db_stamp, False
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from flask import request as flask_request
+
+    user_id = _resolve_stamp_check_user_id()
+    if user_id is None:
+        logger.debug(
+            "Stamp validation skipped: no user_id for %s %s",
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)
+    if _should_skip_stamp_db_lookup(user_id, flask_request.method, sess_stamp):
+        logger.debug(
+            "Session auth stamp DB check skipped (recently validated) for user_id=%s "
+            "on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    db_stamp, db_error = _load_db_stamp_for_user(
+        user_id,
+        use_cache=(
+            flask_request.method in _SAFE_METHODS and _get_revalidation_seconds() > 0
+        ),
+    )
+    if db_error:
+        return
+
+    if db_stamp is None:
+        # No stamp row means the session predates stamp tracking. Allow the
+        # request without a check — sync_session_auth_stamp_on_login creates
+        # the row on every login, so this only occurs for sessions that
+        # pre-date the feature or whose row was removed independently.
+        # Committing inside a before_request hook would expire SQLAlchemy
+        # objects loaded by earlier hooks and can cause DetachedInstanceError
+        # in route handlers, so we never create the row here.
+        logger.debug(
+            "Session auth stamp check skipped: no DB stamp row for user_id=%s on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    # Compare the session cookie stamp against the authoritative DB value.
+    logger.debug(
+        "Validating session stamp for user_id=%s on %s %s: sess_stamp=%s, db_stamp=%s",
+        user_id,
+        flask_request.method,
+        flask_request.path,
+        sess_stamp,
+        db_stamp,
+    )
+    if sess_stamp is None or sess_stamp != db_stamp:
+        # Sessions authenticated before stamp tracking carry no session stamp.
+        # Adopt the current DB value so the request can proceed. This is bounded
+        # to the rollout window: the cookie is signed, so an attacker cannot
+        # strip the stamp to bypass invalidation after password rotation.
+        if sess_stamp is None and db_stamp is not None:
+            session[SESSION_AUTH_STAMP_SESSION_KEY] = db_stamp

Review Comment:
   I had one thought about the stamp adoption logic. It looks like when a session is restored from a remember-me cookie, there's no stamp in the session, so the current code adopts the latest DB stamp and the session remains valid. 
   Would it make sense to only adopt the stamp for `fresh` (`session["_fresh"]`) interactive logins, and instead reject non-fresh restored sessions (logout + clear the remember cookie + `401`)? 
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3616482057


##########
superset/cli/reset.py:
##########
@@ -68,7 +69,11 @@ def factory_reset(
     # Validate the user
     password = click.prompt("Admin Password", hide_input=True)
     user = security_manager.find_user(username)
-    if not user or not check_password_hash(user.password, password):
+    if (
+        not user
+        or not user.is_active
+        or not verify_auth_db_password(user.password, password)

Review Comment:
   ✅ **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag authentication-failure bookkeeping or lockout/rate-limit bypasses in CLI commands that run with host shell access inside the operator trust boundary; those commands can already access the metadata DB directly.
   
   **Applied to:**
     - `superset/cli/**`
   
   ---
   💡 *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3453937116


##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +113,214 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self):
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self):
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self):
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self):
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self):
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Undefined name 'login' reference</b></div>
   <div id="fix">
   
   The name `login` is not defined. It appears this should be `self.login()` to call the test case login method, consistent with other test methods in the file.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
           app = superset_integration_app
           client_a: FlaskClient = app.test_client()
           self.login(ADMIN_USERNAME)
    
           session_cookie = client_a.get_cookie("session")
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -19,18 +19,31 @@
 
 from unittest.mock import patch
 
+from flask_appbuilder.const import AUTH_OAUTH
+
 from superset import security_manager

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing import causes runtime crash</b></div>
   <div id="fix">
   
   The import of `with_config` was removed but the decorator is still used at line 339 for `test_avatar_with_valid_user`. Without this import, running any test that uses the decorator will raise `NameError: name 'with_config' is not defined`.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -135,15 +244,20 @@ function UserInfoModal({
       requiredFields={requiredFields}
       initialValues={initialValues}
     >
-      {isEditMode ? <EditModeFields /> : <ResetPasswordFields />}
+      {(form: FormInstance) =>
+        isEditMode ? <EditModeFields /> : <ResetPasswordFields form={form} />
+      }
     </FormModal>
   );
 }

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing unit tests for new modal</b></div>
   <div id="fix">
   
   The `UserInfoModal` component has no dedicated unit tests despite adding significant new functionality including password policy validation, strength indicator, and password generation. According to rule [11730], comprehensive unit tests for new features must cover success paths, error scenarios, and validation failures.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    no_patch_reason
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset/security/manager.py:
##########
@@ -4213,6 +4226,36 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"],

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>CWE-20: Undefined Config Key</b></div>
   <div id="fix">
   
   Reference to undefined config key `AUTH_DB_FAKE_PASSWORD_HASH_CHECK` will raise `KeyError` at runtime. Define this config value in `config.py` or remove the fake hash check to prevent authentication failures. (See also: [CWE-20](https://cwe.mitre.org/data/definitions/20.html))
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,274 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>No automated sync for common passwords</b></div>
   <div id="fix">
   
   The comment states 'keep in sync with _COMMON_PASSWORDS in that module' but there's no automated mechanism to verify synchronization. Manual synchronization risks drift over time, causing the frontend to allow passwords the backend rejects.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset/security/manager.py:
##########
@@ -4213,6 +4226,36 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"],
+                "password",
+            )
+            logger.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+            if first_user:
+                self.noop_user_update(first_user)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>CWE-393: Missing Method Definition</b></div>
   <div id="fix">
   
   Method `noop_user_update` is called but does not exist in SupersetSecurityManager or its parent BaseSecurityManager. This will raise AttributeError at runtime during failed login attempts. (See also: [CWE-393](https://cwe.mitre.org/data/definitions/393.html))
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #e1b7ec</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3540754834


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,382 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"

Review Comment:
   **Suggestion:** Add explicit type annotations for the new session-stamp key constants to satisfy the type-hint requirement for relevant module-level variables. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   These newly added module-level constants are plain assignments without type annotations. The custom rule requires type hints for new Python variables that can be annotated, so this is a real violation.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d12be5e2ed3545438fa08b638f8d847c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d12be5e2ed3545438fa08b638f8d847c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 35:37
   **Comment:**
   	*Custom Rule: Add explicit type annotations for the new session-stamp key constants to satisfy the type-hint requirement for relevant module-level variables.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=37a7a2ba96b453eb517671a97216fc7362a5a4e9056a3315dd3a7677e0b2e2c2&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=37a7a2ba96b453eb517671a97216fc7362a5a4e9056a3315dd3a7677e0b2e2c2&reaction=dislike'>👎</a>



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,382 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300

Review Comment:
   **Suggestion:** Add explicit type annotations for these cache and method constants so all relevant new variables are typed. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   These are also newly introduced module-level constants without explicit type hints. Since they are relevant variables that can be annotated, they violate the type-hint requirement.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f9cbdc91bc544a37bac78c4af0a55a60&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f9cbdc91bc544a37bac78c4af0a55a60&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 39:41
   **Comment:**
   	*Custom Rule: Add explicit type annotations for these cache and method constants so all relevant new variables are typed.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=35c90ffd727c4943d06d45586063c3d94736bc66103426e14236918122b1d5bb&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=35c90ffd727c4943d06d45586063c3d94736bc66103426e14236918122b1d5bb&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3534298710


##########
superset/security/manager.py:
##########
@@ -4543,6 +4560,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )

Review Comment:
   **Suggestion:** The failed-login balancing path always runs `check_password_hash` against a fixed PBKDF2 hash and a constant plaintext (`"password"`), while real users are now verified with bcrypt/argon2 in `verify_auth_db_password`. This creates measurable timing differences between nonexistent/inactive accounts and real accounts, enabling username enumeration. Use a fake verification path that matches the active AUTH_DB hash family and uses the candidate password input so failure timing remains consistent. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ AUTH_DB login allows username enumeration via timing differences.
   - ⚠️ Attacker can target valid users for credential stuffing.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Create or reuse an AUTH_DB user (for example `alice`) so `user.password` stores a
   bcrypt or argon2 hash via `hash_auth_db_password` in
   `superset/utils/auth_db_password_hash.py:50-69`.
   
   2. From a Python shell or test, call `SupersetSecurityManager.auth_user_db("no-such-user",
   "candidate-password")` with a username that does not exist; in `auth_user_db`
   (superset/security/manager.py:4563-4576) `user` is `None`, so the missing-user branch at
   4576-4584 executes
   `check_password_hash(current_app.config.get("AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
   DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK), "password")` against a fixed PBKDF2 hash and
   constant plaintext.
   
   3. Call `SupersetSecurityManager.auth_user_db("alice", "wrong-password")` for an existing
   active user; in `auth_user_db` the user is found and the success/failure path at 4589-4594
   invokes `verify_auth_db_password(user.password, password)`, which for bcrypt hashes goes
   through the bcrypt branch in `verify_auth_db_password`
   (superset/utils/auth_db_password_hash.py:81-93) and performs a full bcrypt or argon2
   verification.
   
   4. Instrument these two calls (for example using `time.perf_counter`) or exercise them via
   the AUTH_DB `/login` flow (implemented by Flask-AppBuilder calling `auth_user_db`) and
   observe that the nonexistent-user path (step 2, PBKDF2 fake check with constant
   `"password"`) has a measurably different runtime than the real-user path (step 3,
   bcrypt/argon2 verification), enabling remote username enumeration based solely on response
   timing.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=857170a382854c1e9090c87a20579fb3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=857170a382854c1e9090c87a20579fb3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/manager.py
   **Line:** 4578:4584
   **Comment:**
   	*Security: The failed-login balancing path always runs `check_password_hash` against a fixed PBKDF2 hash and a constant plaintext (`"password"`), while real users are now verified with bcrypt/argon2 in `verify_auth_db_password`. This creates measurable timing differences between nonexistent/inactive accounts and real accounts, enabling username enumeration. Use a fake verification path that matches the active AUTH_DB hash family and uses the candidate password input so failure timing remains consistent.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=04b794ae5cec28f24082bde8984d2f0058a705530d3587fb6631ad80bbf786ec&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=04b794ae5cec28f24082bde8984d2f0058a705530d3587fb6631ad80bbf786ec&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3474349712


##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        """Reject password changes via PUT /me with a 400 when AUTH_TYPE is AUTH_DB."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        """Reject the change with a 400 when current_password is incorrect."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        """Verify a new password failing policy is rejected with a 400."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:
+        """Cover the successful password change flow for the current user."""
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")
+        assert session_cookie is not None
+
+        client_b: FlaskClient = app.test_client()
+        client_b.set_cookie(
+            key="session",
+            value=session_cookie.value,
+            domain=session_cookie.domain or "localhost",
+            path=session_cookie.path or "/",
+        )
+
+        new_password = "AnotherStr0ng!PassClone"  # noqa: S105
+        try:
+            rv = client_a.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            assert client_b.get(meUri).status_code == 401
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_clears_remember_cookie(self) -> None:
+        """
+        Password change schedules Flask-Login remember-me cookie deletion.
+
+        Superset does not expose remember-me in the React login UI; this is defensive
+        hardening for FAB / Flask-Login persistent cookies.
+        """
+        app = superset_integration_app
+        remember_name = app.config.get("REMEMBER_COOKIE_NAME", "remember_token")
+        self.login(ADMIN_USERNAME)
+        self.client.set_cookie(remember_name, "stale-remember-token")
+
+        new_password = "AnotherStr0ng!PassRemember"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            set_cookies = rv.headers.getlist("Set-Cookie")
+            cleared = any(
+                remember_name in header
+                and ("=;" in header or "Max-Age=0" in header or "1970" in header)
+                for header in set_cookies
+            )
+            assert cleared, f"Expected remember cookie clear in {set_cookies}"
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_invalid_hash_algorithm(self) -> None:

Review Comment:
   **Suggestion:** Add a short docstring to this newly added test method to describe the invalid hash algorithm scenario and expected behavior. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This newly added test method has no docstring in the final file state.
   The rule requires newly added Python functions and classes to include docstrings, so the suggestion correctly identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=4310b6f95dc64352b384c9c47c489197&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=4310b6f95dc64352b384c9c47c489197&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 255:255
   **Comment:**
   	*Custom Rule: Add a short docstring to this newly added test method to describe the invalid hash algorithm scenario and expected behavior.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8b8cade2e11c5f4ab882189d972de97c59036d165197fa43f2a25d0185cab82f&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8b8cade2e11c5f4ab882189d972de97c59036d165197fa43f2a25d0185cab82f&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        """Reject password changes via PUT /me with a 400 when AUTH_TYPE is AUTH_DB."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        """Reject the change with a 400 when current_password is incorrect."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        """Verify a new password failing policy is rejected with a 400."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:
+        """Cover the successful password change flow for the current user."""
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")
+        assert session_cookie is not None
+
+        client_b: FlaskClient = app.test_client()
+        client_b.set_cookie(
+            key="session",
+            value=session_cookie.value,
+            domain=session_cookie.domain or "localhost",
+            path=session_cookie.path or "/",
+        )
+
+        new_password = "AnotherStr0ng!PassClone"  # noqa: S105
+        try:
+            rv = client_a.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            assert client_b.get(meUri).status_code == 401
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_clears_remember_cookie(self) -> None:
+        """
+        Password change schedules Flask-Login remember-me cookie deletion.
+
+        Superset does not expose remember-me in the React login UI; this is defensive
+        hardening for FAB / Flask-Login persistent cookies.
+        """
+        app = superset_integration_app
+        remember_name = app.config.get("REMEMBER_COOKIE_NAME", "remember_token")
+        self.login(ADMIN_USERNAME)
+        self.client.set_cookie(remember_name, "stale-remember-token")
+
+        new_password = "AnotherStr0ng!PassRemember"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            set_cookies = rv.headers.getlist("Set-Cookie")
+            cleared = any(
+                remember_name in header
+                and ("=;" in header or "Max-Age=0" in header or "1970" in header)
+                for header in set_cookies
+            )
+            assert cleared, f"Expected remember cookie clear in {set_cookies}"
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_invalid_hash_algorithm(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth_db_config = superset_integration_app.config.get(
+            "AUTH_DB_CONFIG", {}
+        )
+        try:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = {
+                **original_auth_db_config,
+                "password_hash_algorithm": "invalid",
+            }
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = original_auth_db_config
+
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "password_hash_algorithm" in data["message"]
+
+    def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth = superset_integration_app.config["AUTH_TYPE"]
+        try:
+            superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_TYPE"] = original_auth
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_get_my_password_policy_success(self) -> None:

Review Comment:
   **Suggestion:** Add a concise docstring to this new test method describing the successful password policy retrieval case. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The added test method is missing a docstring in the final code.
   This matches the custom rule requiring documentation for newly added Python functions and classes.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=841ed9bac8b740df81bc93acea2c59ac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=841ed9bac8b740df81bc93acea2c59ac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 299:299
   **Comment:**
   	*Custom Rule: Add a concise docstring to this new test method describing the successful password policy retrieval case.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=93257ce9b0e69a44c3d4db83cf12c63bf5d25a8730c4b22e5095ab4833ba808a&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=93257ce9b0e69a44c3d4db83cf12c63bf5d25a8730c4b22e5095ab4833ba808a&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        """Reject password changes via PUT /me with a 400 when AUTH_TYPE is AUTH_DB."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        """Reject the change with a 400 when current_password is incorrect."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        """Verify a new password failing policy is rejected with a 400."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:
+        """Cover the successful password change flow for the current user."""
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")
+        assert session_cookie is not None
+
+        client_b: FlaskClient = app.test_client()
+        client_b.set_cookie(
+            key="session",
+            value=session_cookie.value,
+            domain=session_cookie.domain or "localhost",
+            path=session_cookie.path or "/",
+        )
+
+        new_password = "AnotherStr0ng!PassClone"  # noqa: S105
+        try:
+            rv = client_a.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            assert client_b.get(meUri).status_code == 401
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_clears_remember_cookie(self) -> None:
+        """
+        Password change schedules Flask-Login remember-me cookie deletion.
+
+        Superset does not expose remember-me in the React login UI; this is defensive
+        hardening for FAB / Flask-Login persistent cookies.
+        """
+        app = superset_integration_app
+        remember_name = app.config.get("REMEMBER_COOKIE_NAME", "remember_token")
+        self.login(ADMIN_USERNAME)
+        self.client.set_cookie(remember_name, "stale-remember-token")
+
+        new_password = "AnotherStr0ng!PassRemember"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            set_cookies = rv.headers.getlist("Set-Cookie")
+            cleared = any(
+                remember_name in header
+                and ("=;" in header or "Max-Age=0" in header or "1970" in header)
+                for header in set_cookies
+            )
+            assert cleared, f"Expected remember cookie clear in {set_cookies}"
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_invalid_hash_algorithm(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth_db_config = superset_integration_app.config.get(
+            "AUTH_DB_CONFIG", {}
+        )
+        try:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = {
+                **original_auth_db_config,
+                "password_hash_algorithm": "invalid",
+            }
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = original_auth_db_config
+
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "password_hash_algorithm" in data["message"]
+
+    def test_put_my_password_unavailable_when_not_auth_db(self) -> None:

Review Comment:
   **Suggestion:** Add a docstring to this newly added test method explaining that the password endpoint is unavailable when authentication is not AUTH_DB. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This method is newly added and does not have a docstring.
   Since the rule explicitly requires docstrings for newly added Python functions, the violation is real.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f8768f0ba3ba40ecb1757fc1281701b3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f8768f0ba3ba40ecb1757fc1281701b3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 280:280
   **Comment:**
   	*Custom Rule: Add a docstring to this newly added test method explaining that the password endpoint is unavailable when authentication is not AUTH_DB.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3c274863a02ee168d40018c7811cd36dd3e4faec6f78fce010af07629bdc1d0e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3c274863a02ee168d40018c7811cd36dd3e4faec6f78fce010af07629bdc1d0e&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self) -> None:
+        """Reject password changes via PUT /me with a 400 when AUTH_TYPE is AUTH_DB."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self) -> None:
+        """Reject the change with a 400 when current_password is incorrect."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": "not-the-admin-password",
+                "new_password": "AnotherStr0ng!Pass",
+                "confirm_password": "AnotherStr0ng!Pass",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["message"] == "Incorrect current password."
+
+    def test_put_my_password_weak_new(self) -> None:
+        """Verify a new password failing policy is rejected with a 400."""
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(
+            mePasswordUri,
+            json={
+                "current_password": DEFAULT_PASSWORD,
+                "new_password": "short",
+                "confirm_password": "short",
+            },
+        )
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "new_password" in data["message"]
+
+    def test_put_my_password_success(self) -> None:
+        """Cover the successful password change flow for the current user."""
+        self.login(ADMIN_USERNAME)
+        new_password = "AnotherStr0ng!Pass"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            rv2 = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": new_password,
+                    "new_password": "YetAnotherStr0ng!Pw",
+                    "confirm_password": "YetAnotherStr0ng!Pw",
+                },
+            )
+            assert rv2.status_code == 200
+        finally:
+            self._restore_admin_default_password()
+
+    def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+        """
+        Rotating the session stamp on password change logs out other clients that
+        still present the pre-change signed session cookie.
+        """
+        from flask.testing import FlaskClient
+
+        app = superset_integration_app
+        client_a: FlaskClient = app.test_client()
+        login(client_a, ADMIN_USERNAME)
+
+        session_cookie = client_a.get_cookie("session")
+        assert session_cookie is not None
+
+        client_b: FlaskClient = app.test_client()
+        client_b.set_cookie(
+            key="session",
+            value=session_cookie.value,
+            domain=session_cookie.domain or "localhost",
+            path=session_cookie.path or "/",
+        )
+
+        new_password = "AnotherStr0ng!PassClone"  # noqa: S105
+        try:
+            rv = client_a.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            assert client_b.get(meUri).status_code == 401
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_clears_remember_cookie(self) -> None:
+        """
+        Password change schedules Flask-Login remember-me cookie deletion.
+
+        Superset does not expose remember-me in the React login UI; this is defensive
+        hardening for FAB / Flask-Login persistent cookies.
+        """
+        app = superset_integration_app
+        remember_name = app.config.get("REMEMBER_COOKIE_NAME", "remember_token")
+        self.login(ADMIN_USERNAME)
+        self.client.set_cookie(remember_name, "stale-remember-token")
+
+        new_password = "AnotherStr0ng!PassRemember"  # noqa: S105
+        try:
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": new_password,
+                    "confirm_password": new_password,
+                },
+            )
+            assert rv.status_code == 200
+
+            set_cookies = rv.headers.getlist("Set-Cookie")
+            cleared = any(
+                remember_name in header
+                and ("=;" in header or "Max-Age=0" in header or "1970" in header)
+                for header in set_cookies
+            )
+            assert cleared, f"Expected remember cookie clear in {set_cookies}"
+        finally:
+            self._restore_admin_default_password(app)
+
+    def test_put_my_password_invalid_hash_algorithm(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth_db_config = superset_integration_app.config.get(
+            "AUTH_DB_CONFIG", {}
+        )
+        try:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = {
+                **original_auth_db_config,
+                "password_hash_algorithm": "invalid",
+            }
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_DB_CONFIG"] = original_auth_db_config
+
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "password_hash_algorithm" in data["message"]
+
+    def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
+        self.login(ADMIN_USERNAME)
+        original_auth = superset_integration_app.config["AUTH_TYPE"]
+        try:
+            superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+            rv = self.client.put(
+                mePasswordUri,
+                json={
+                    "current_password": DEFAULT_PASSWORD,
+                    "new_password": "AnotherStr0ng!Pass",
+                    "confirm_password": "AnotherStr0ng!Pass",
+                },
+            )
+        finally:
+            superset_integration_app.config["AUTH_TYPE"] = original_auth
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_get_my_password_policy_success(self) -> None:
+        self.login(ADMIN_USERNAME)
+        rv = self.client.get("/api/v1/me/password/policy")
+        assert rv.status_code == 200
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["result"] == get_public_auth_db_password_policy()
+
+    def test_get_my_password_policy_unavailable_when_not_auth_db(self) -> None:

Review Comment:
   **Suggestion:** Add a docstring to this newly added test method clarifying that the password policy endpoint should fail outside AUTH_DB mode. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This newly added test method also lacks a docstring.
   The suggestion is valid because the rule requires docstrings for new Python functions.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=554a3ce866444942823c6daf9c38719c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=554a3ce866444942823c6daf9c38719c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 306:306
   **Comment:**
   	*Custom Rule: Add a docstring to this newly added test method clarifying that the password policy endpoint should fail outside AUTH_DB mode.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=78eec783e36141c2ffb327e77741c5dfaaf557654578663c731360ff7e28eb1f&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=78eec783e36141c2ffb327e77741c5dfaaf557654578663c731360ff7e28eb1f&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3474347871


##########
superset/models/user_session_auth_stamp.py:
##########
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Per-user stamp used to invalidate browser sessions after password changes."""
+
+from __future__ import annotations
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from superset import security_manager
+
+
+class UserSessionAuthStamp(Model):
+    """
+    One row per ``ab_user`` holding a stamp copied into the Flask session at login.
+
+    Bumping ``stamp`` invalidates every session that still carries an older value.
+    """
+
+    __tablename__ = "user_session_auth_stamp"
+
+    user_id = Column(
+        Integer, ForeignKey("ab_user.id", ondelete="CASCADE"), primary_key=True
+    )
+    stamp = Column(String(36), nullable=False)
+
+    user = relationship(
+        security_manager.user_model,
+        foreign_keys=[user_id],
+    )
+
+    def __repr__(self) -> str:
+        return f"<UserSessionAuthStamp user_id={self.user_id}>"

Review Comment:
   **Suggestion:** Add a short docstring to this newly added method so all new functions in the file are documented inline. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added Python method in the final file state, and it does not include a docstring. The custom rule requires new functions and classes to be documented inline, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=5d490d76d4114efc83cef4747d846aec&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=5d490d76d4114efc83cef4747d846aec&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/user_session_auth_stamp.py
   **Line:** 47:48
   **Comment:**
   	*Custom Rule: Add a short docstring to this newly added method so all new functions in the file are documented inline.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6fd6c9c6eb538a8c5ca3f8e8db366e70f0ba55bcadaab7c170fa0140b0564f25&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=6fd6c9c6eb538a8c5ca3f8e8db366e70f0ba55bcadaab7c170fa0140b0564f25&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.test.tsx:
##########
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { SupersetClient } from '@superset-ui/core';
+import {
+  render,
+  screen,
+  fireEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import { ChangePasswordModal } from './UserInfoModal';
+
+const mockAddDangerToast = jest.fn();
+const mockAddSuccessToast = jest.fn();
+
+jest.mock('src/components/MessageToasts/withToasts', () => ({
+  __esModule: true,
+  ...jest.requireActual('src/components/MessageToasts/withToasts'),
+  useToasts: () => ({
+    addDangerToast: mockAddDangerToast,
+    addSuccessToast: mockAddSuccessToast,
+  }),
+}));
+
+const policyResult = {
+  password_min_length: 12,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+const STRONG_PASSWORD = 'AnotherStr0ng!Pass';
+
+const renderModal = (onSave = jest.fn()) => {
+  render(<ChangePasswordModal show onHide={jest.fn()} onSave={onSave} />, {
+    useRedux: true,
+  });
+  return { onSave };
+};
+
+const fillPasswords = (newPassword = STRONG_PASSWORD) => {
+  fireEvent.change(screen.getByPlaceholderText('Enter your current password'), {
+    target: { value: 'CurrentStr0ng!Pass' },
+  });
+  fireEvent.change(screen.getByPlaceholderText('Enter a new password'), {
+    target: { value: newPassword },
+  });
+  fireEvent.change(screen.getByPlaceholderText('Confirm the new password'), {
+    target: { value: newPassword },
+  });
+};
+
+beforeEach(() => {
+  jest.clearAllMocks();
+  jest
+    .spyOn(SupersetClient, 'get')
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    .mockResolvedValue({ json: { result: policyResult } } as any);
+});
+
+afterEach(() => {
+  jest.restoreAllMocks();
+});
+
+test('fetches the password policy when opened', async () => {
+  renderModal();
+  await waitFor(() => {
+    expect(SupersetClient.get).toHaveBeenCalledWith(
+      expect.objectContaining({ endpoint: '/api/v1/me/password/policy' }),
+    );
+  });
+});
+
+test('submits a valid password change and lets FormModal trigger onSave', async () => {
+  const put = jest
+    .spyOn(SupersetClient, 'put')
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    .mockResolvedValue({ json: {} } as any);

Review Comment:
   **Suggestion:** Replace this `any` cast with a concrete mocked response type for the `SupersetClient.put` call so the test does not bypass TypeScript checks. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This newly added TSX test code also uses `as any` in the mocked `SupersetClient.put` response. The rule prohibits any new or modified TypeScript/TSX code from using `any`, so the violation is real.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=981948bcf69a4964a6cee2235f86c564&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=981948bcf69a4964a6cee2235f86c564&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.test.tsx
   **Line:** 95:95
   **Comment:**
   	*Custom Rule: Replace this `any` cast with a concrete mocked response type for the `SupersetClient.put` call so the test does not bypass TypeScript checks.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d4754b9a11744e67a56ade7c19b83bbe451fe41f544ceb29a84a861048734d30&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=d4754b9a11744e67a56ade7c19b83bbe451fe41f544ceb29a84a861048734d30&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.test.tsx:
##########
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { SupersetClient } from '@superset-ui/core';
+import {
+  render,
+  screen,
+  fireEvent,
+  waitFor,
+} from 'spec/helpers/testing-library';
+import { ChangePasswordModal } from './UserInfoModal';
+
+const mockAddDangerToast = jest.fn();
+const mockAddSuccessToast = jest.fn();
+
+jest.mock('src/components/MessageToasts/withToasts', () => ({
+  __esModule: true,
+  ...jest.requireActual('src/components/MessageToasts/withToasts'),
+  useToasts: () => ({
+    addDangerToast: mockAddDangerToast,
+    addSuccessToast: mockAddSuccessToast,
+  }),
+}));
+
+const policyResult = {
+  password_min_length: 12,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+const STRONG_PASSWORD = 'AnotherStr0ng!Pass';
+
+const renderModal = (onSave = jest.fn()) => {
+  render(<ChangePasswordModal show onHide={jest.fn()} onSave={onSave} />, {
+    useRedux: true,
+  });
+  return { onSave };
+};
+
+const fillPasswords = (newPassword = STRONG_PASSWORD) => {
+  fireEvent.change(screen.getByPlaceholderText('Enter your current password'), {
+    target: { value: 'CurrentStr0ng!Pass' },
+  });
+  fireEvent.change(screen.getByPlaceholderText('Enter a new password'), {
+    target: { value: newPassword },
+  });
+  fireEvent.change(screen.getByPlaceholderText('Confirm the new password'), {
+    target: { value: newPassword },
+  });
+};
+
+beforeEach(() => {
+  jest.clearAllMocks();
+  jest
+    .spyOn(SupersetClient, 'get')
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    .mockResolvedValue({ json: { result: policyResult } } as any);

Review Comment:
   **Suggestion:** Replace this `any` cast with a concrete mocked response type for the `SupersetClient.get` call (or a narrower generic/assertion) so the test remains type-safe. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added TypeScript test file, and the mocked `SupersetClient.get` call uses `as any`. The custom rule explicitly flags any new or modified TS/TSX code that uses `any`, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=71228fd2c31a416499dae6a72506c907&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=71228fd2c31a416499dae6a72506c907&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.test.tsx
   **Line:** 75:75
   **Comment:**
   	*Custom Rule: Replace this `any` cast with a concrete mocked response type for the `SupersetClient.get` call (or a narrower generic/assertion) so the test remains type-safe.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9e40e7e28834b523a668ac77689f9bcd092e10eafabdb60747116fe9b400e49e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9e40e7e28834b523a668ac77689f9bcd092e10eafabdb60747116fe9b400e49e&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3550079643


##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,202 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  css,
+  styled,
+  useTheme,
+  type SupersetTheme,
+} from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import {
+  Icons,
+  Popover,
+  Progress,
+  Typography,
+} from '@superset-ui/core/components';
+import {
+  AUTH_DB_BCRYPT_MAX_PASSWORD_BYTES,
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`

Review Comment:
   do we need `theme` here? 



##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,202 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  css,
+  styled,
+  useTheme,
+  type SupersetTheme,
+} from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import {
+  Icons,
+  Popover,
+  Progress,
+  Typography,
+} from '@superset-ui/core/components';
+import {
+  AUTH_DB_BCRYPT_MAX_PASSWORD_BYTES,
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`
+    flex: 1;
+    cursor: help;
+
+    .ant-progress {
+      margin-bottom: 0;
+    }
+  `}
+`;

Review Comment:
   do we have another soulution instead of using the css class? 



##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,202 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  css,
+  styled,
+  useTheme,
+  type SupersetTheme,
+} from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import {
+  Icons,
+  Popover,
+  Progress,
+  Typography,
+} from '@superset-ui/core/components';
+import {
+  AUTH_DB_BCRYPT_MAX_PASSWORD_BYTES,
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;

Review Comment:
   would make sense to use the `<Flex />` component here? 
   



##########
superset-frontend/src/utils/generateAuthDbPassword.test.ts:
##########
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AUTH_DB_PASSWORD_MIN_LENGTH,
+  generateAuthDbPassword,
+  getAuthDbPasswordPolicyChecks,
+  getAuthDbPasswordPolicyError,
+  satisfiesDefaultAuthDbPasswordPolicy,
+} from './generateAuthDbPassword';
+
+test('generateAuthDbPassword returns policy-compliant passwords', () => {
+  for (let i = 0; i < 20; i += 1) {
+    const pwd = generateAuthDbPassword();
+    expect(Array.from(pwd).length).toBeGreaterThanOrEqual(
+      AUTH_DB_PASSWORD_MIN_LENGTH,
+    );
+    expect(satisfiesDefaultAuthDbPasswordPolicy(pwd)).toBe(true);
+  }
+});
+
+test('generateAuthDbPassword honors a custom minimum length policy', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_min_length: 20,
+  };
+  for (let i = 0; i < 10; i += 1) {
+    const pwd = generateAuthDbPassword(policy);
+    expect(Array.from(pwd).length).toBeGreaterThanOrEqual(20);
+    expect(getAuthDbPasswordPolicyError(pwd, policy)).toBeNull();
+  }
+});
+
+test('getAuthDbPasswordPolicyChecks counts Unicode code points for min length', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_min_length: 2,
+    password_require_uppercase: false,
+    password_require_lowercase: false,
+    password_require_digit: false,
+    password_require_special: false,
+    password_common_list_check: false,
+  };
+  expect(getAuthDbPasswordPolicyChecks('a😀', policy).minLength).toBe(true);
+  expect(getAuthDbPasswordPolicyChecks('😀', policy).minLength).toBe(false);
+});
+
+test('getAuthDbPasswordPolicyError respects disabled uppercase requirement', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_require_uppercase: false,
+  };
+  const password = 'abcdefghijklm1!';
+  expect(getAuthDbPasswordPolicyError(password, policy)).toBeNull();
+});
+
+test('getAuthDbPasswordPolicyError rejects bcrypt passwords over the byte limit', () => {
+  const password = `Aa1!${'x'.repeat(69)}`;
+  expect(getAuthDbPasswordPolicyError(password)).toMatch(/72 bytes/);
+});
+
+test('getAuthDbPasswordPolicyError skips bcrypt byte limit for argon2', () => {
+  const policy = {
+    ...AUTH_DB_DEFAULT_PASSWORD_POLICY,
+    password_hash_algorithm: 'argon2' as const,
+    password_require_uppercase: false,
+    password_require_lowercase: false,
+    password_require_digit: false,
+    password_require_special: false,
+    password_common_list_check: false,
+    password_min_length: 1,

Review Comment:
   Would it make sense to centralize this instead of repeating it in multiple places?
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4876546421

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4850272484

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3498658774


##########
superset/views/users/api.py:
##########
@@ -14,26 +14,146 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
+import functools
+import logging
 from datetime import datetime
-from typing import Any, Dict
+from typing import Any, Callable, Dict
 
-from flask import current_app as app, g, redirect, request, Response
+from flask import current_app as app, g, redirect, request, Response, session
 from flask_appbuilder.api import expose, permission_name, safe
+from flask_appbuilder.const import AUTH_DB
 from flask_appbuilder.security.decorators import protect
 from flask_appbuilder.security.sqla.models import User
+from flask_login import login_user
 from marshmallow import ValidationError
+from sqlalchemy.exc import SQLAlchemyError
 from sqlalchemy.orm.exc import NoResultFound
-from werkzeug.security import generate_password_hash
 
 from superset import is_feature_enabled
 from superset.daos.user import UserDAO
-from superset.extensions import db, event_logger
+from superset.extensions import db, event_logger, security_manager
+from superset.utils.auth_db_password import (
+    get_auth_db_login_rate_limit_string,
+    get_public_auth_db_password_policy,
+    validate_auth_db_password,
+)
+from superset.utils.auth_db_password_hash import (
+    hash_auth_db_password,
+    verify_auth_db_password,
+)
+from superset.utils.auth_session_stamp import (
+    bump_user_session_auth_stamp,
+    clear_flask_login_remember_cookie,
+    sync_session_auth_stamp_on_login,
+)
+from superset.utils.decorators import transaction
 from superset.utils.slack import get_user_avatar, SlackClientError
 from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics
-from superset.views.users.schemas import CurrentUserPutSchema, UserResponseSchema
+from superset.views.users.schemas import (
+    CurrentUserPasswordPutSchema,
+    CurrentUserPutSchema,
+    UserResponseSchema,
+)
 from superset.views.utils import bootstrap_user_data
 
-user_response_schema = UserResponseSchema()
+logger: logging.Logger = logging.getLogger(__name__)
+
+user_response_schema: UserResponseSchema = UserResponseSchema()
+
+
+def _me_password_rate_limit_key() -> str:
+    """Return the per-user rate-limit key for password changes."""
+    return f"me_password_uid:{g.user.id}"
+
+
+def _rate_limit_me_password_change(
+    f: Callable[..., Response],
+) -> Callable[..., Response]:
+    """Apply AUTH_DB ``login_rate_limit`` when Flask-Limiter is enabled."""
+
+    @functools.wraps(f)
+    def wrapped(self: CurrentUserRestApi, *args: Any, **kwargs: Any) -> Response:
+        """Invoke ``f`` directly or through Flask-Limiter when rate limiting is on."""
+        if not app.config.get("RATELIMIT_ENABLED", False):
+            return f(self, *args, **kwargs)
+        limiter = getattr(security_manager, "limiter", None)
+        if limiter is None:
+            return f(self, *args, **kwargs)
+        limited_view = limiter.limit(
+            get_auth_db_login_rate_limit_string(),
+            key_func=_me_password_rate_limit_key,
+            methods=["PUT"],
+        )(f)
+        return limited_view(self, *args, **kwargs)
+
+    return wrapped
+
+
+class PasswordChangeConflictError(Exception):
+    """Raised when an optimistic password update loses a concurrent write race."""
+
+
+def _load_password_change_body(
+    schema: CurrentUserPasswordPutSchema,
+    payload: object,
+) -> dict[str, str]:
+    """Parse and policy-validate a password-change request payload."""
+    body = schema.load(payload or {})
+    validate_auth_db_password(body["new_password"])
+    return body
+
+
+@transaction()
+def _commit_user_password_change(
+    api: CurrentUserRestApi,
+    user_id: int,
+    old_hash: str,
+    new_hash: str,
+) -> User:
+    """Persist a password change and rotate the user's session auth stamp.
+
+    The whole flow runs in a single transaction that commits once on success,
+    so any failure path (including a missing user) rolls back the password
+    write rather than reporting an error after it was already committed.
+    """
+    api.pre_update(g.user, {})
+    rows_updated = (
+        db.session.query(User)
+        .filter(User.id == user_id, User.password == old_hash)
+        .update(
+            {
+                User.password: new_hash,
+                User.changed_on: g.user.changed_on,
+                User.changed_by_fk: g.user.changed_by_fk,
+            },
+            synchronize_session=False,
+        )
+    )
+    if rows_updated != 1:
+        raise PasswordChangeConflictError
+
+    bump_user_session_auth_stamp(user_id)
+    from superset.security.password_change import clear_password_must_change
+
+    clear_password_must_change(user_id)
+    user_after = db.session.get(User, user_id)
+    if user_after is None:
+        logger.error("User missing after password update for id=%s", user_id)
+        raise SQLAlchemyError("user missing after password update")
+    return user_after
+
+
+def _reestablish_login_session(user: User) -> None:
+    """Clear the cookie session and log the user back in after a password change."""
+    for key in list(session.keys()):
+        session.pop(key)
+    login_user(user)

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing on_user_login audit hook</b></div>
   <div id="fix">
   
   The `login_user(user)` call does not invoke `security_manager.on_user_login(user)`, which normally stamps login time, syncs session auth, and logs a `UserLoggedIn` audit event. The existing comment (line 153) acknowledges the missing `on_user_login` call and copies the post-commit auth stamp, but the audit event and login time stamp are still omitted. If this omission is intentional (mid-request re-auth vs. fresh login), add a comment explaining why; otherwise, call `security_manager.on_user_login(user)` after `login_user`.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #538335</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/components/AuthDbPasswordPolicyIndicator.tsx:
##########
@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  css,
+  styled,
+  useTheme,
+  type SupersetTheme,
+} from '@apache-superset/core/theme';
+import { t } from '@apache-superset/core/translation';
+import {
+  Icons,
+  Popover,
+  Progress,
+  Typography,
+} from '@superset-ui/core/components';
+import {
+  AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  AuthDbPasswordPolicy,
+  getAuthDbPasswordPolicyChecks,
+} from 'src/utils/generateAuthDbPassword';
+
+interface AuthDbPasswordPolicyIndicatorProps {
+  password: string;
+  policy?: AuthDbPasswordPolicy;
+}
+
+const StrengthWrapper = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+  `}
+`;
+
+const StrengthBarContainer = styled.div`
+  ${({ theme }) => css`
+    flex: 1;
+    cursor: help;
+
+    .ant-progress {
+      margin-bottom: 0;
+    }
+  `}
+`;
+
+const Checklist = styled.div`
+  ${({ theme }) => css`
+    min-width: ${theme.sizeUnit * 70}px;
+    display: flex;
+    flex-direction: column;
+    gap: ${theme.sizeUnit}px;
+  `}
+`;
+
+const ChecklistItem = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+    gap: ${theme.sizeUnit * 2}px;
+    font-size: ${theme.fontSize}px;
+  `}
+`;
+
+const requirementText = {
+  minLength: (minLength: number) => t('At least %s characters', minLength),
+  uppercase: t('Contains an uppercase letter'),
+  lowercase: t('Contains a lowercase letter'),
+  digit: t('Contains a digit'),
+  special: t('Contains a special character'),
+  commonPassword: t('Is not a common password'),
+};
+
+function getStrengthState(percent: number, theme: SupersetTheme) {
+  if (percent <= 33) {
+    return { color: theme.colorError, label: t('Very weak') };
+  }
+  if (percent <= 50) {
+    return { color: theme.colorWarningText, label: t('Weak') };
+  }
+  if (percent <= 67) {
+    return { color: theme.colorWarning, label: t('Medium') };
+  }
+  if (percent <= 83) {
+    return { color: theme.colorSuccess, label: t('Strong') };
+  }
+  return { color: theme.colorSuccessTextActive, label: t('Very strong') };
+}
+
+export default function AuthDbPasswordPolicyIndicator({
+  password,
+  policy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+}: AuthDbPasswordPolicyIndicatorProps) {
+  const theme = useTheme();
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  const hasPassword = password.length > 0;
+  const checklist = [
+    {
+      label: requirementText.minLength(policy.password_min_length),
+      passed: hasPassword && checks.minLength,
+    },
+    ...(policy.password_require_uppercase
+      ? [
+          {
+            label: requirementText.uppercase,
+            passed: hasPassword && checks.uppercase,
+          },
+        ]
+      : []),
+    ...(policy.password_require_lowercase
+      ? [
+          {
+            label: requirementText.lowercase,
+            passed: hasPassword && checks.lowercase,
+          },
+        ]
+      : []),
+    ...(policy.password_require_digit
+      ? [{ label: requirementText.digit, passed: hasPassword && checks.digit }]
+      : []),
+    ...(policy.password_require_special
+      ? [
+          {
+            label: requirementText.special,
+            passed: hasPassword && checks.special,
+          },
+        ]
+      : []),
+    ...(policy.password_common_list_check
+      ? [
+          {
+            label: requirementText.commonPassword,
+            passed: hasPassword && checks.commonPassword,
+          },
+        ]
+      : []),
+  ];
+  const passedChecks = checklist.filter(item => item.passed).length;
+  const percent = Math.round((passedChecks / checklist.length) * 100);
+  const strength = getStrengthState(percent, theme);
+
+  return (
+    <StrengthWrapper>
+      <Popover
+        trigger="hover"
+        placement="topLeft"
+        content={
+          <Checklist>
+            {checklist.map(item => (
+              <ChecklistItem key={item.label}>
+                {item.passed ? (
+                  <Icons.CheckCircleFilled iconColor="success" iconSize="m" />
+                ) : (
+                  <Icons.CloseCircleOutlined iconColor="danger" iconSize="m" />
+                )}
+                <Typography.Text>{item.label}</Typography.Text>
+              </ChecklistItem>
+            ))}
+          </Checklist>
+        }
+      >
+        <StrengthBarContainer>
+          <Progress
+            percent={percent}
+            showInfo={false}
+            strokeColor={strength.color}
+            size="small"
+          />
+        </StrengthBarContainer>
+      </Popover>
+      <Typography.Text type="secondary">{strength.label}</Typography.Text>
+    </StrengthWrapper>
+  );
+}

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing unit tests for new component</b></div>
   <div id="fix">
   
   New component with business logic (strength calculation at lines 153-155, conditional checklist generation at lines 112-152) lacks unit test coverage. BITO.md rule [11730] requires comprehensive tests for new tools covering success paths, error scenarios, and edge cases. A component that visualizes password strength and validates policy requirements needs tests to verify correctness of the percentage and label logic.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #27f12a</i></small>
   </div><div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing unit tests</b></div>
   <div id="fix">
   
   No unit tests exist for this component. Per [11730], new components need tests covering success paths, error scenarios, validation failures, and edge cases. Create `AuthDbPasswordPolicyIndicator.test.tsx` using Jest + React Testing Library patterns (no Enzyme).
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #538335</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/constants/auth.ts:
##########
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Mirrors Flask-AppBuilder ``AUTH_TYPE`` values from bootstrap ``common.conf``. */
+export enum AuthType {

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>AuthType naming collision</b></div>
   <div id="fix">
   
   The name `AuthType` collides with an existing enum in `src/features/databases/DatabaseModal/index.tsx:183-186` that represents SSH login methods (`Password`, `PrivateKey`). These are semantically different domains (authentication method vs SSH credential type). Exporting both with the same name creates potential type confusion for callers. The enum comment correctly notes it mirrors Flask-AppBuilder's `AUTH_TYPE`, so `AuthMethod` would clearly distinguish its purpose from the SSH tunnel enum.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #538335</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3502748047


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,288 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/**
+ * Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in
+ * ``superset/utils/auth_db_password.py``. Sync is enforced by
+ * ``tests/unit_tests/utils/test_auth_db_password.py``.
+ */
+export const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+/** Mirrors ``int(cfg.get("password_min_length", default))`` in auth_db_password.py. */
+function resolveAuthDbPasswordMinLength(
+  passwordMinLength: number | undefined | null,
+): number {
+  if (passwordMinLength === undefined || passwordMinLength === null) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  const parsed = Number(passwordMinLength);
+  if (!Number.isFinite(parsed)) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  return Math.trunc(parsed);
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const cryptoObj = globalThis.crypto;
+  if (!cryptoObj?.getRandomValues) {
+    throw new Error(
+      'secureRandomInt: a Web Crypto implementation with getRandomValues ' +
+        'is required to generate passwords',
+    );
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    cryptoObj.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(policy: AuthDbPasswordPolicy): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\p{Nd}/u.test(password),
+    special:
+      !policy.password_require_special || /[^\p{L}\p{N}\s]/u.test(password),
+    commonPassword:
+      !policy.password_common_list_check ||
+      !AUTH_DB_COMMON_PASSWORDS.has(password.toLowerCase().trim()),
+  };

Review Comment:
   **Suggestion:** Client-side policy checks do not include bcrypt’s 72-byte UTF-8 limit that the backend enforces, so some passwords are marked valid in the UI but are rejected by the API. Add a byte-length rule (conditioned on bcrypt hashing) to keep frontend validation aligned with server-side enforcement. [incomplete implementation]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ AUTH_DB password change rejects UI-approved long passwords.
   - ⚠️ Password strength indicator misleads about backend acceptance.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure Superset with AUTH_DB authentication (default `AUTH_TYPE = AUTH_DB` in
   `superset/config.py:463`) so `/api/v1/me/password` is enabled and `AUTH_DB_CONFIG` uses
   the default bcrypt hash algorithm (`superset/utils/auth_db_password.py:28-42`, `144-166`).
   
   2. Open the password reset UI, which renders `UserInfoModal`
   (`superset-frontend/src/features/userInfo/UserInfoModal.tsx:45-52`) and, when not in edit
   mode, fetches the password policy from `GET /api/v1/me/password/policy`
   (`UserInfoModal.tsx:57-76`, backend at `superset/views/users/api.py:72-107`) to populate
   `passwordPolicy` used by the frontend validator.
   
   3. In the modal, enter a very long password that satisfies all visible rules but exceeds
   bcrypt’s 72-byte UTF-8 limit (for example an 80-character ASCII string with uppercase,
   lowercase, digits, and specials); the new-password field’s validator calls
   `getPasswordPolicyError(password)` (`UserInfoModal.tsx:162-178`), which delegates to
   `getAuthDbPasswordPolicyError` and `getAuthDbPasswordPolicyChecks` in
   `superset-frontend/src/utils/generateAuthDbPassword.ts:195-212`, where checks only cover
   minLength, character classes, and common-password list—no byte-length rule.
   
   4. Submit the form, which issues `PUT /api/v1/me/password` (`UserInfoModal.tsx:90-107`);
   the backend parses the payload via `_load_password_change_body` and enforces policy with
   `validate_auth_db_password(body["new_password"])` (`superset/views/users/api.py:19-26`),
   and `validate_auth_db_password` includes `_bcrypt_byte_limit_errors` that reject passwords
   longer than `BCRYPT_MAX_PASSWORD_BYTES` (`superset/utils/auth_db_password.py:191-260`),
   causing a 400 error with a backend byte-limit message even though the frontend indicator
   and validator previously accepted the same password.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b543a3d8dc8a47799ec78d443ec31c9b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b543a3d8dc8a47799ec78d443ec31c9b&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 99:211
   **Comment:**
   	*Incomplete Implementation: Client-side policy checks do not include bcrypt’s 72-byte UTF-8 limit that the backend enforces, so some passwords are marked valid in the UI but are rejected by the API. Add a byte-length rule (conditioned on bcrypt hashing) to keep frontend validation aligned with server-side enforcement.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=4ba26a587902aaa94bd15d8210c5cf8f55d36ae25414198408bd97e9231a32ab&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=4ba26a587902aaa94bd15d8210c5cf8f55d36ae25414198408bd97e9231a32ab&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4841228654

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4775634375

   @shantanukhond I think this is one CI culprit: in `tests/integration_tests/users/api_tests.py:186`, `test_put_my_password_invalidates_cloned_session_client` calls a bare `login(client_a, ADMIN_USERNAME)` that's never imported or defined. Every other test uses `self.login(...)`, so this one `NameError`s. Fix that, get pre-commit + the DB test jobs green, and clear the remaining bot threads, then ping me for a re-look. Thanks yet again.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "sadpandajoe (via GitHub)" <gi...@apache.org>.
sadpandajoe commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3686582479


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,268 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS: tuple[str, ...] = (

Review Comment:
   When `AUTH_DB_CONFIG` selects Argon2, this policy response still omits `password_hash_algorithm`, so the frontend falls back to bcrypt and enforces bcrypt's 72-byte limit. Users can therefore be blocked from submitting passwords that the configured Argon2 backend accepts. Could the public policy include the configured algorithm and have the frontend consume it?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106987126


##########
superset/daos/auth_audit_log.py:
##########
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""DAO for authentication audit log rows."""
+
+from __future__ import annotations
+
+from datetime import datetime
+from typing import Any
+
+from superset.extensions import db
+from superset.models.auth_audit_log import AuthAuditLog
+
+
+class AuthAuditLogDAO:
+    """Persist authentication audit events."""
+
+    @staticmethod
+    def create(
+        *,
+        event_type: str,
+        user_id: int | None = None,
+        ip_address: str | None = None,
+        user_agent: str | None = None,
+        metadata: dict[str, Any] | None = None,
+    ) -> AuthAuditLog:
+        """
+        Insert an audit row and flush so ``id`` is populated.
+
+        The caller is responsible for committing the surrounding transaction.
+        """
+        entry = AuthAuditLog(
+            user_id=user_id,
+            event_type=event_type,
+            ip_address=ip_address,
+            user_agent=user_agent,
+            event_metadata=metadata,
+            created_at=datetime.utcnow(),

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Use timezone-aware datetime instead utcnow</b></div>
   <div id="fix">
   
   Replace `datetime.utcnow()` with `datetime.now(datetime.UTC)` or `datetime.now(timezone.utc)` to use timezone-aware datetime objects. This avoids potential timezone-related bugs.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
     from datetime import datetime
    +from datetime import timezone
     from typing import Any
    
     from superset.extensions import db
    @@ -48,7 +49,7 @@
                 user_id=user_id,
                 event_type=event_type,
                 ip_address=ip_address,
                 user_agent=user_agent,
                 event_metadata=metadata,
    -            created_at=datetime.utcnow(),
    +            created_at=datetime.now(timezone.utc),
             )
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #6616fd</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106963836


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+
+/** Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in auth_db_password.py */
+const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+function secureRandomInt(maxExclusive: number): number {
+  const buf = new Uint32Array(1);
+  crypto.getRandomValues(buf);
+  return buf[0]! % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(password: string): boolean {
+  if (password.length < AUTH_DB_PASSWORD_MIN_LENGTH) {
+    return false;
+  }
+  if (!/[A-Z]/.test(password)) {
+    return false;
+  }
+  if (!/[a-z]/.test(password)) {
+    return false;
+  }
+  if (!/\d/.test(password)) {
+    return false;
+  }
+  if (![...password].some(c => !/[A-Za-z0-9]/.test(c) && !/\s/.test(c))) {

Review Comment:
   **Suggestion:** The special-character policy check is ASCII-only, so non-ASCII letters/digits are incorrectly treated as special characters on the client. This can mark a password as policy-compliant in the UI while backend validation still rejects it; use a Unicode-aware check for non-alphanumeric/non-whitespace characters. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Frontend helper can misclassify Unicode characters as special.
   - ⚠️ /api/v1/me/password may reject passwords helper deems valid.
   - ⚠️ Potential future UX confusion if helper used for UI checks.
   ```
   </details>
   
   ```suggestion
     if (![...password].some(c => /[^\p{L}\p{N}\s]/u.test(c))) {
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Note the backend AUTH_DB password validation in
   `superset/utils/auth_db_password.py:25-48`, where `password_require_special` is enforced
   via `any((not c.isalnum() and not c.isspace()) for c in password)` at lines 38-40.
   Python's `str.isalnum()` and `str.isspace()` are Unicode-aware, so non‑ASCII
   letters/digits (for example `é`, `ß`, `Ж`) are treated as alphanumeric, not as "special"
   characters.
   
   2. Observe the frontend helper `satisfiesDefaultAuthDbPasswordPolicy` in
   `superset-frontend/src/utils/generateAuthDbPassword.ts:96-116`, where the
   special‑character requirement is implemented as `![...password].some(c =>
   !/[A-Za-z0-9]/.test(c) && !/\\s/.test(c))` at line 110. This regex only considers ASCII
   letters/digits; any non‑ASCII letter or digit fails `/[A-Za-z0-9]/` and is therefore
   treated as "special" on the client.
   
   3. In a frontend/unit context (e.g. extending
   `superset-frontend/src/utils/generateAuthDbPassword.test.ts`), import
   `satisfiesDefaultAuthDbPasswordPolicy` from `src/utils/generateAuthDbPassword` and
   evaluate a password like `"Äbcdef3ÄÄÄÄÄÄ"`. This string has uppercase (`Ä`), lowercase
   (`b`), a digit (`3`), and uses the non‑ASCII letter `Ä` where the current frontend check
   counts it as "special" because `!/[A-Za-z0-9]/.test('Ä') && !/\\s/.test('Ä')` is true. The
   helper returns `true`, indicating the password satisfies the default policy.
   
   4. Submit the same password as `new_password` to the real endpoint `PUT
   /api/v1/me/password` implemented in `superset/views/users/api.py:51-68`, which calls
   `validate_auth_db_password(body["new_password"])` at line 238. The backend validator's
   Unicode‑aware `(not c.isalnum() and not c.isspace())` check finds no special characters in
   `"Äbcdef3ÄÄÄÄÄÄ"`, accumulates `"Password must contain at least one special
   (non-alphanumeric) character."`, and returns HTTP 400. This demonstrates a concrete
   divergence: the exported frontend helper says the password is policy‑compliant, while the
   backend endpoint rejects it. (Currently, the helper is only used in tests and not yet
   wired into UI validation, so the mismatch is a latent bug in the shared helper rather than
   a live UX issue.)
   ```
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Futils%2FgenerateAuthDbPassword.ts%0A%2A%2ALine%3A%2A%2A%20110%3A110%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20The%20special-character%20policy%20check%20is%20ASCII-only%2C%20so%20non-ASCII%20letters%2Fdigits%20are%20incorrectly%20treated%20as%20special%20characters%20on%20the%20client.%20This%20can%20mark%20a%20password%20as%20policy-compliant%20in%20the%20UI%20while%20backend%20validation%20still%20rejects%20it%3B%20use%20a%20Unicode-aware%20check%20for%20non-alphanumeric%2Fnon-whitespace%20characters.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prom
 pt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Futils%2FgenerateAuthDbPassword.ts%0A%2A%2ALine%3A%2A%2A%20110%3A110%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20The%20special-character%20policy%20check%20is%20ASCII-only%2C%20so%20non-ASCII%20letters%2Fdigits%20are%20incorrectly%20treated%20as%20special%20characters%20on%20the%20client.%20This%20can%20mark%20a%20password%20as%20policy-compliant%20in%20the%20UI%20while%20backend%20validation%20still%20rejects%20it%3B%20use%20a%20Unicode-aware%20check%20for%20non-alphanumeric%2Fnon-whitespace%20characters.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/utils/generateAuthDbPassword.ts
   **Line:** 110:110
   **Comment:**
   	*Logic Error: The special-character policy check is ASCII-only, so non-ASCII letters/digits are incorrectly treated as special characters on the client. This can mark a password as policy-compliant in the UI while backend validation still rejects it; use a Unicode-aware check for non-alphanumeric/non-whitespace characters.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ed42d620312cac9b56b5d7cf6e04b99f5380d876953b53e4385e6fc0d8a15297&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ed42d620312cac9b56b5d7cf6e04b99f5380d876953b53e4385e6fc0d8a15297&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/users/UserListResetPasswordModal.tsx:
##########
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+import { getClientErrorObject, SupersetClient } from '@superset-ui/core';
+import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
+import { useToasts } from 'src/components/MessageToasts/withToasts';
+import {
+  FormModal,
+  FormItem,
+  Input,
+  Icons,
+  type FormInstance,
+} from '@superset-ui/core/components';
+import { GeneratePasswordInputSuffix } from 'src/components/GeneratePasswordInputSuffix';
+import { generateAuthDbPassword } from 'src/utils/generateAuthDbPassword';
+import { UserObject } from 'src/pages/UsersList/types';
+import { buildSecurityUserUpdatePayload } from './utils';
+
+export interface UserListResetPasswordModalProps {
+  show: boolean;
+  onHide: () => void;
+  onSave: () => void;
+  user: UserObject | null;
+}
+
+function UserListResetPasswordModal({
+  show,
+  onHide,
+  onSave,
+  user,
+}: UserListResetPasswordModalProps) {
+  const { addDangerToast, addSuccessToast } = useToasts();
+
+  const handleFormSubmit = async (values: {
+    password: string;
+    confirmPassword: string;
+  }) => {
+    if (!user) {
+      return;
+    }
+    const { confirmPassword, ...rest } = values;
+    void confirmPassword;
+    const payload = {
+      ...buildSecurityUserUpdatePayload(user),
+      password: rest.password,
+    };
+    try {
+      await SupersetClient.put({
+        endpoint: `/api/v1/security/users/${user.id}`,
+        jsonPayload: payload,
+      });
+      addSuccessToast(
+        t('Password for %(username)s was updated successfully', {
+          username: user.username,
+        }),
+      );
+      onSave();

Review Comment:
   **Suggestion:** `FormModal` already invokes `onSave` after `formSubmitHandler` resolves, so calling `onSave` inside `handleFormSubmit` triggers duplicate success side effects (double refresh/close). Remove the manual callback here and let `FormModal` handle it once. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Users list refresh triggered twice per password reset.
   - ⚠️ Duplicate `/security/users` fetch via `refreshData()` each reset.
   - ⚠️ Redundant modal close and state resets executed twice.
   ```
   </details>
   
   ```suggestion
   
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In `src/pages/UsersList/index.tsx:90-103`, the `UsersList` component renders
   `<UserListResetPasswordModal>` with an `onSave` callback that calls `refreshData();
   closeModal(ModalType.RESET_PASSWORD); setCurrentUser(null);` after a successful password
   reset.
   
   2. In the same file at `index.tsx:58-66`, an admin clicks the "Reset password" action in
   the users table, which calls `handleResetPassword()` and sets `currentUser` then opens the
   reset-password modal (`modalState.resetPassword = true`), so the modal is shown for that
   user.
   
   3. Inside `src/features/users/UserListResetPasswordModal.tsx:88-107`, the modal passes
   `onSave={onSave}` and `formSubmitHandler={handleFormSubmit}` into `<FormModal>`, while
   `handleFormSubmit` is defined at `UserListResetPasswordModal.tsx:50-86` and, on success,
   executes the SupersetClient `PUT /api/v1/security/users/{id}` call, shows a success toast,
   and then calls `onSave()` at line 73.
   
   4. The shared `FormModal` implementation in
   `packages/superset-ui-core/src/components/Modal/FormModal.tsx:51-62` defines
   `handleFormSubmit` that awaits `formSubmitHandler(values)` and then calls `handleSave()`,
   which in turn calls the passed `onSave()`; since `handleFormSubmit` in
   `UserListResetPasswordModal` already calls `onSave()`, the parent `onSave` from
   `UsersList` is invoked twice on every successful password reset, causing `refreshData()`
   and modal-closing side effects to run twice.
   ```
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2Fusers%2FUserListResetPasswordModal.tsx%0A%2A%2ALine%3A%2A%2A%2073%3A73%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20%60FormModal%60%20already%20invokes%20%60onSave%60%20after%20%60formSubmitHandler%60%20resolves%2C%20so%20calling%20%60onSave%60%20inside%20%60handleFormSubmit%60%20triggers%20duplicate%20success%20side%20effects%20%28double%20refresh%2Fclose%29.%20Remove%20the%20manual%20callback%20here%20and%20let%20%60FormModal%60%20handle%20it%20once.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%2
 0a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2Fusers%2FUserListResetPasswordModal.tsx%0A%2A%2ALine%3A%2A%2A%2073%3A73%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20%60FormModal%60%20already%20invokes%20%60onSave%60%20after%20%60formSubmitHandler%60%20resolves%2C%20so%20calling%20%60onSave%60%20inside%20%60handleFormSubmit%60%20triggers%20duplicate%20success%20side%20effects%20%28double%20refresh%2Fclose%29.%20Remove%20the%20manual%20callback%20here%20and%20let%20%60FormModal%60%20handle%20it%20once.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/users/UserListResetPasswordModal.tsx
   **Line:** 73:73
   **Comment:**
   	*Logic Error: `FormModal` already invokes `onSave` after `formSubmitHandler` resolves, so calling `onSave` inside `handleFormSubmit` triggers duplicate success side effects (double refresh/close). Remove the manual callback here and let `FormModal` handle it once.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5850b025fc1690eb904a2cab3f311e70b5ea44272a5021d0ecc313e6dbcbb8e3&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5850b025fc1690eb904a2cab3f311e70b5ea44272a5021d0ecc313e6dbcbb8e3&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4730012961

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3433251502


##########
superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py:
##########
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add auth_audit_log table
+
+Revision ID: b7e4f2a891c3
+Revises: 78a40c08b4be

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Alembic migration chain broken</b></div>
   <div id="fix">
   
   This migration (dated 2026-04-19) cannot correctly depend on migration `78a40c08b4be` (dated 2026-06-03, ~6 weeks later). Alembic migration chains must maintain chronological ordering — a migration cannot depend on a future migration. This will cause Alembic to fail when attempting to apply migrations in sequence.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    --- superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
    +++ superset/migrations/versions/2026-04-19_14-30_b7e4f2a891c3_add_auth_audit_log_table.py
    @@ -17,7 +17,7 @@
     """add auth_audit_log table
    
     Revision ID: b7e4f2a891c3
    -Revises: 78a40c08b4be
    +Revises: ce6bd21901ab
     Create Date: 2026-04-19 14:30:00.000000
    
     """
    @@ -31,7 +31,7 @@
    
     # revision identifiers, used by Alembic.
     revision = "b7e4f2a891c3"
    -down_revision = "78a40c08b4be"
    +down_revision = "ce6bd21901ab"
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #859504</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4738252971

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4722733837

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "shantanukhond (via GitHub)" <gi...@apache.org>.
shantanukhond commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4753141503

   1. Regarding `/me/password` change, I can do both please recommend I will do needful to get it ready.
   2. And I dropped `auth_audit_log` this one in latest commit. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4773294244

   Sorry there's still a wall of bot comments left to assess/address here... once that's all ironed out, and CI is passing (much of it due to precommit, I think), then we can reassess.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3460165510


##########
superset/views/users/api.py:
##########
@@ -14,47 +14,163 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
+import functools
+import logging
 from datetime import datetime
-from typing import Any, Dict
+from typing import Any, Callable, Dict
 
-from flask import current_app as app, g, redirect, request, Response
+from flask import current_app as app, g, redirect, request, Response, session
 from flask_appbuilder.api import expose, permission_name, safe
+from flask_appbuilder.const import AUTH_DB
 from flask_appbuilder.security.decorators import protect
 from flask_appbuilder.security.sqla.models import User
+from flask_login import login_user
 from marshmallow import ValidationError
+from sqlalchemy.exc import SQLAlchemyError
 from sqlalchemy.orm.exc import NoResultFound
-from werkzeug.security import generate_password_hash
 
 from superset import is_feature_enabled
 from superset.daos.user import UserDAO
-from superset.extensions import db, event_logger
+from superset.extensions import db, event_logger, security_manager
+from superset.utils.auth_db_password import (
+    get_auth_db_login_rate_limit_string,
+    get_public_auth_db_password_policy,
+    validate_auth_db_password,
+)
+from superset.utils.auth_db_password_hash import (
+    hash_auth_db_password,
+    verify_auth_db_password,
+)
+from superset.utils.auth_session_stamp import (
+    bump_user_session_auth_stamp,
+    clear_flask_login_remember_cookie,
+)
 from superset.utils.slack import get_user_avatar, SlackClientError
 from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics
-from superset.views.users.schemas import CurrentUserPutSchema, UserResponseSchema
+from superset.views.users.schemas import (
+    CurrentUserPasswordPutSchema,
+    CurrentUserPutSchema,
+    UserResponseSchema,
+)
 from superset.views.utils import bootstrap_user_data
 
+logger = logging.getLogger(__name__)
+
 user_response_schema = UserResponseSchema()
 
 
+def _get_client_ip() -> str | None:
+    """Return best-effort client IP from request context."""
+    if request.access_route:
+        return request.access_route[0]
+    return request.remote_addr
+
+
+def _me_password_rate_limit_key() -> str:
+    uid = getattr(getattr(g, "user", None), "id", None)
+    if uid is not None:
+        return f"me_password_uid:{uid}"
+    return _get_client_ip() or "unknown"
+
+
+def _rate_limit_me_password_change(
+    f: Callable[..., Response],
+) -> Callable[..., Response]:
+    """Apply AUTH_DB ``login_rate_limit`` when Flask-Limiter is enabled."""
+
+    @functools.wraps(f)
+    def wrapped(self: CurrentUserRestApi, *args: Any, **kwargs: Any) -> Response:
+        """Invoke ``f`` directly or through Flask-Limiter when rate limiting is on."""
+        if not app.config.get("RATELIMIT_ENABLED", False):
+            return f(self, *args, **kwargs)
+        limiter = getattr(security_manager, "limiter", None)
+        if limiter is None:
+            return f(self, *args, **kwargs)
+        limited_view = limiter.limit(
+            get_auth_db_login_rate_limit_string(),
+            key_func=_me_password_rate_limit_key,
+            methods=["PUT"],
+        )(f)
+        return limited_view(self, *args, **kwargs)
+
+    return wrapped
+
+
+class PasswordChangeConflictError(Exception):
+    """Raised when an optimistic password update loses a concurrent write race."""
+
+
+def _load_password_change_body(
+    schema: CurrentUserPasswordPutSchema,
+    payload: object,
+) -> dict[str, str]:
+    """Parse and policy-validate a password-change request payload."""
+    body = schema.load(payload or {})
+    validate_auth_db_password(body["new_password"])
+    return body
+
+
+def _commit_user_password_change(
+    api: CurrentUserRestApi,
+    user_id: int,
+    old_hash: str,
+    new_hash: str,
+) -> User:
+    """Persist a password change and rotate the user's session auth stamp."""
+    api.pre_update(g.user, {})
+    rows_updated = (
+        db.session.query(User)
+        .filter(User.id == user_id, User.password == old_hash)
+        .update(
+            {
+                User.password: new_hash,
+                User.changed_on: g.user.changed_on,
+                User.changed_by_fk: g.user.changed_by_fk,
+            },
+            synchronize_session=False,
+        )
+    )
+    if rows_updated != 1:
+        db.session.rollback()  # pylint: disable=consider-using-transaction
+        raise PasswordChangeConflictError
+
+    bump_user_session_auth_stamp(user_id)
+    db.session.commit()  # pylint: disable=consider-using-transaction
+    user_after = db.session.get(User, user_id)
+    if user_after is None:
+        logger.error("User missing after password commit for id=%s", user_id)
+        raise SQLAlchemyError("user missing after password commit")

Review Comment:
   **Suggestion:** The password update and session-stamp rotation are not committed atomically: `bump_user_session_auth_stamp()` is transaction-decorated and can commit early, but this function can still raise afterward (for example when `user_after` is missing), which makes the API return an error after the password was already changed. Keep the whole password-change flow in one transaction and commit once at the end so failure paths cannot report 500 after a successful password write. [race condition]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ /api/v1/me/password can 500 after persisting change.
   - ⚠️ Users may retry thinking prior password change failed.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Log in with AUTH_DB enabled and call `PUT /api/v1/me/password`, handled by
   `CurrentUserRestApi.update_my_password` in `superset/views/users/api.py:36-125`, which
   after basic validation calls `_commit_user_password_change` at
   `superset/views/users/api.py:115-145`.
   
   2. Inside `_commit_user_password_change`, the code updates `User.password` via
   `db.session.query(User)...update(...)` and, if one row was updated, calls
   `bump_user_session_auth_stamp(user_id)` at `superset/views/users/api.py:139` before any
   explicit commit.
   
   3. `bump_user_session_auth_stamp` in `superset/utils/auth_session_stamp.py:99-121` is
   decorated with `@transaction()` from `superset/utils/decorators.py:16-56`, which sets
   `g.in_transaction`, executes the function, and then calls `db.session.commit()` on
   success; this commit includes the earlier `User.password` update made in
   `_commit_user_password_change`.
   
   4. After `bump_user_session_auth_stamp` returns, `_commit_user_password_change` runs
   `user_after = db.session.get(User, user_id)` and raises `SQLAlchemyError` if it returns
   `None` (`superset/views/users/api.py:141-144`); in a test or concurrent-delete scenario
   (e.g. monkeypatching `db.session.get` to raise or return `None`), the API returns a 500
   via the `except SQLAlchemyError` block in `update_my_password`
   (`superset/views/users/api.py:103-121`) even though the password (and session stamp) have
   already been committed by the `@transaction` decorator.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d69b98689a014cc6a6d540bdc1949d8f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d69b98689a014cc6a6d540bdc1949d8f&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 139:144
   **Comment:**
   	*Race Condition: The password update and session-stamp rotation are not committed atomically: `bump_user_session_auth_stamp()` is transaction-decorated and can commit early, but this function can still raise afterward (for example when `user_after` is missing), which makes the API return an error after the password was already changed. Keep the whole password-change flow in one transaction and commit once at the end so failure paths cannot report 500 after a successful password write.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ac89afd0a7ce843cae7c321cf52d979aba8752193bb7176cbbdc3383bdf87d10&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=ac89afd0a7ce843cae7c321cf52d979aba8752193bb7176cbbdc3383bdf87d10&reaction=dislike'>👎</a>



##########
superset/views/users/schemas.py:
##########
@@ -51,8 +51,33 @@ class CurrentUserPutSchema(Schema):
         metadata={"description": last_name_description},
         validate=[Length(1, 64)],
     )
-    password = fields.String(
-        required=False,
-        validate=[PasswordComplexityValidator()],
-        metadata={"description": password_description},
+
+
+class CurrentUserPasswordPutSchema(Schema):
+    """Payload for ``PUT /api/v1/me/password`` (AUTH_DB only)."""
+
+    current_password = fields.String(
+        required=True,
+        metadata={"description": current_password_description},
+        validate=[Length(min=1, max=256)],
     )
+    new_password = fields.String(
+        required=True,
+        metadata={"description": new_password_description},
+        validate=[Length(min=1, max=256)],
+    )

Review Comment:
   **Suggestion:** The schema hard-codes `new_password` to `Length(min=1, ...)`, which conflicts with the configurable password policy (the policy layer explicitly supports `password_min_length = 0`). This creates an API contract mismatch where a password accepted by configured policy can still be rejected at schema validation. Align schema constraints with policy enforcement (or remove the hardcoded min for `new_password`). [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ /api/v1/me/password may reject policy-compliant passwords.
   - ⚠️ Frontend policy UI disagrees with backend request schema.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure `AUTH_DB_CONFIG` so that `password_min_length` is set to `0` (or any value
   below `1`), which is merged into the effective policy by `get_merged_auth_db_config` in
   `superset/utils/auth_db_password.py:100-103` and used by `validate_auth_db_password` at
   `superset/utils/auth_db_password.py:160-173`.
   
   2. With `AUTH_TYPE` set to `AUTH_DB`, authenticate as a user and send `PUT
   /api/v1/me/password` (handled by `CurrentUserRestApi.update_my_password` in
   `superset/views/users/api.py:36-125`) with JSON like `{"current_password": "...",
   "new_password": "", "confirm_password": ""}`.
   
   3. `update_my_password` calls `_load_password_change_body`
   (`superset/views/users/api.py:105-112`), which in turn calls
   `CurrentUserPasswordPutSchema.load()`; that schema in
   `superset/views/users/schemas.py:56-73` defines `new_password = fields.String(...,
   validate=[Length(min=1, max=256)])`, so marshmallow raises a `ValidationError` for
   `new_password` being shorter than 1 character, and `update_my_password` returns a 400 from
   its `except ValidationError` block at `superset/views/users/api.py:86-101`.
   
   4. If `validate_auth_db_password` is invoked directly with the same empty-string password
   under `password_min_length = 0`, it will accept the value (no length error at
   `superset/utils/auth_db_password.py:171-173`), demonstrating a real mismatch between
   schema-level request validation and the configurable AUTH_DB password policy that the
   endpoint's own docstring claims to enforce.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=90b34de843104094b636364e0723cb1e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=90b34de843104094b636364e0723cb1e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/schemas.py
   **Line:** 64:68
   **Comment:**
   	*Api Mismatch: The schema hard-codes `new_password` to `Length(min=1, ...)`, which conflicts with the configurable password policy (the policy layer explicitly supports `password_min_length = 0`). This creates an API contract mismatch where a password accepted by configured policy can still be rejected at schema validation. Align schema constraints with policy enforcement (or remove the hardcoded min for `new_password`).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=987a268a0497f7086091814bb0d8976c1c8e9067b8fb49cf505d9abf733b5743&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=987a268a0497f7086091814bb0d8976c1c8e9067b8fb49cf505d9abf733b5743&reaction=dislike'>👎</a>



##########
superset-frontend/src/constants/auth.ts:
##########
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Mirrors Flask-AppBuilder ``AUTH_TYPE`` values from bootstrap ``common.conf``. */
+export enum AuthType {
+  AuthOID = 0,
+  AuthDB = 1,
+  AuthLDAP = 2,
+  AuthOauth = 4,
+  AuthSAML = 5,

Review Comment:
   **Suggestion:** This enum claims to mirror Flask-AppBuilder `AUTH_TYPE` values, but it omits the valid `AUTH_REMOTE_USER` value (`3`). That creates an incomplete contract for consumers of `AuthType` and can cause wrong branching or impossible-type assumptions when bootstrap data contains that auth mode. Add the missing enum member so frontend auth-mode checks stay aligned with backend constants. [incomplete implementation]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Frontend cannot type-safely represent AUTH_REMOTE_USER mode.
   - ⚠️ Future auth-mode switches risk mishandling remote-user configuration.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In the backend config at `superset/config.py:432-437`, note that comments describe
   `AUTH_DB`, `AUTH_LDAP`, and `AUTH_REMOTE_USER` as valid authentication types, and docs
   under `docs/admin_docs/configuration/networking-settings.mdx:169` explicitly reference
   `AUTH_REMOTE_USER` as a supported mode.
   
   2. Observe that bootstrap exposes `AUTH_TYPE` to the frontend via
   `cached_common_bootstrap_data`, tested in
   `tests/unit_tests/views/test_bootstrap_auth.py:63-76`, where
   `payload["conf"]["AUTH_TYPE"]` is asserted for other modes like `AUTH_OAUTH` and
   `AUTH_SAML`.
   
   3. On the frontend, open `superset-frontend/src/constants/auth.ts:20-26`, where `AuthType`
   is documented to "mirror Flask-AppBuilder AUTH_TYPE values" but only defines `AuthOID =
   0`, `AuthDB = 1`, `AuthLDAP = 2`, `AuthOauth = 4`, and `AuthSAML = 5`, with no entry for
   `AUTH_REMOTE_USER`.
   
   4. In `superset-frontend/src/pages/UserInfo/index.tsx:44-46`, see `const authType:
   AuthType = getBootstrapData().common.conf.AUTH_TYPE;` which relies on this enum; any
   future frontend code needing to branch on `AUTH_REMOTE_USER` cannot write
   `AuthType.AuthRemoteUser` (it does not exist), leading to incorrect assumptions about the
   possible AUTH_TYPE values and incomplete handling of a backend-supported mode.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=987fc1c977ef4e0297d194695d88c8bc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=987fc1c977ef4e0297d194695d88c8bc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/constants/auth.ts
   **Line:** 20:26
   **Comment:**
   	*Incomplete Implementation: This enum claims to mirror Flask-AppBuilder `AUTH_TYPE` values, but it omits the valid `AUTH_REMOTE_USER` value (`3`). That creates an incomplete contract for consumers of `AuthType` and can cause wrong branching or impossible-type assumptions when bootstrap data contains that auth mode. Add the missing enum member so frontend auth-mode checks stay aligned with backend constants.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c22f40162716727b3c988259418d82ddd24b5d9dd9822c58e5c03f0025daebbc&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=c22f40162716727b3c988259418d82ddd24b5d9dd9822c58e5c03f0025daebbc&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -86,30 +142,82 @@ function UserInfoModal({
     </>
   );
 
-  const ResetPasswordFields = () => (
+  const ResetPasswordFields = ({ form }: { form: FormInstance }) => (
     <>
       <FormItem
-        name="password"
-        label={t('Password')}
-        rules={[{ required: true, message: t('Password is required') }]}
+        name="current_password"
+        label={t('Current password')}
+        rules={[{ required: true, message: t('Current password is required') }]}
+      >
+        <Input.Password
+          name="current_password"
+          autoComplete="current-password"
+          placeholder={t('Enter your current password')}
+        />
+      </FormItem>
+      <FormItem
+        name="new_password"
+        label={t('New password')}
+        rules={[
+          { required: true, message: t('New password is required') },
+          {
+            validator(_, value) {
+              const password = String(value ?? '');
+              if (!password) {
+                return Promise.resolve();
+              }
+              const errorMessage = getPasswordPolicyError(password);
+              return errorMessage
+                ? Promise.reject(new Error(errorMessage))
+                : Promise.resolve();
+            },
+          },
+        ]}
       >
         <Input.Password
-          name="password"
-          placeholder={t("Enter the user's password")}
+          name="new_password"
+          autoComplete="new-password"
+          placeholder={t('Enter a new password')}
+          suffix={
+            <GeneratePasswordInputSuffix
+              onGenerate={() => {
+                const pwd = generateAuthDbPassword(passwordPolicy);
+                form.setFieldsValue({ new_password: pwd, confirm_password: pwd });

Review Comment:
   **Suggestion:** The generated password is written with `form.setFieldsValue`, but `FormModal` enables/disables Save using `onValuesChange`/`onFieldsChange`. Programmatic `setFieldsValue` updates do not reliably trigger those handlers, so after clicking generate the form can remain in a stale "Save disabled" state until the user manually edits a field. Trigger validation/change recalculation after setting values (or explicitly recompute submit state) so generated values immediately make the form submittable. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Reset password modal stays disabled after generating a password.
   - ⚠️ AUTH_DB users discouraged from using generated strong passwords.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset with `AUTH_TYPE = AUTH_DB` (default, configured in
   `superset/config.py:432-437`), log in with a database-backed user, and navigate to
   **Settings → User information**, which renders the `UserInfo` page
   (`superset-frontend/src/pages/UserInfo/index.tsx`).
   
   2. In `UserInfo/index.tsx:44-69`, note that when `authType === AuthType.AuthDB`, a "Reset
   my password" button (`data-test="reset-password-button"`) opens `ChangePasswordModal`,
   which is implemented by `UserInfoModal` with `isEditMode={false}`
   (`superset-frontend/src/pages/UserInfo/index.tsx:47-55` and
   `src/features/userInfo/UserInfoModal.tsx:254-256`).
   
   3. In the reset-password flow, `FormModal` is used with
   `requiredFields=['current_password','new_password','confirm_password']`
   (`UserInfoModal.tsx:81-83, 237-245`), and `FormModal` internally enables/disables Save
   based on `onValuesChange`/`onFieldsChange`
   (`packages/superset-ui-core/src/components/Modal/FormModal.tsx:29-36,48-55`), updating
   `submitDisabled` whenever those handlers fire.
   
   4. Open the "Reset password" modal, click the thunderbolt "Generate password" suffix in
   the "New password" field (`GeneratePasswordInputSuffix` wired at
   `UserInfoModal.tsx:181-187`), which calls `onGenerate` and executes `form.setFieldsValue({
   new_password: pwd, confirm_password: pwd });` (`UserInfoModal.tsx:183-185`); because this
   programmatic `setFieldsValue` update does not trigger the
   `onValuesChange`/`onFieldsChange` handlers that `FormModal` relies on, the Save button
   (`data-test="form-modal-save-button"`) remains disabled despite both required password
   fields being visibly filled until the user manually edits a field.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=4650a8e6f30a42bd8bdada64c668bd25&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=4650a8e6f30a42bd8bdada64c668bd25&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 183:185
   **Comment:**
   	*Api Mismatch: The generated password is written with `form.setFieldsValue`, but `FormModal` enables/disables Save using `onValuesChange`/`onFieldsChange`. Programmatic `setFieldsValue` updates do not reliably trigger those handlers, so after clicking generate the form can remain in a stale "Save disabled" state until the user manually edits a field. Trigger validation/change recalculation after setting values (or explicitly recompute submit state) so generated values immediately make the form submittable.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f9fd5577731a71a6a0fa8c0a64aca098aad5b5df80a5d6bdda3d086331b1a8b6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=f9fd5577731a71a6a0fa8c0a64aca098aad5b5df80a5d6bdda3d086331b1a8b6&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3608478866


##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -112,9 +112,9 @@ def downgrade() -> None:
     session = Session(bind=bind)
     do_downgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction
     except SQLAlchemyError as ex:
-        session.rollback()
+        session.rollback()  # pylint: disable=consider-using-transaction

Review Comment:
   **Suggestion:** Update the downgrade transaction handling to use the shared migration utilities rather than invoking SQLAlchemy session commit/rollback directly. [custom_rule]
   
   **Severity Level:** Major ⚠️
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The downgrade path also uses direct Session.commit()/rollback() calls in the migration instead of shared migration helpers, so it matches the same migration-utils rule violation.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .github/copilot-instructions.md (line 294)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f98d5fcf573a400e9fb53ba11d21b4b4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f98d5fcf573a400e9fb53ba11d21b4b4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py
   **Line:** 115:117
   **Comment:**
   	*Custom Rule: Update the downgrade transaction handling to use the shared migration utilities rather than invoking SQLAlchemy session commit/rollback directly.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b9e9daf4c584b698fd2a26860c49dfa2a734b93698ddcb9e724603fcf4b705f8&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b9e9daf4c584b698fd2a26860c49dfa2a734b93698ddcb9e724603fcf4b705f8&reaction=dislike'>👎</a>



##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -101,9 +101,9 @@ def upgrade() -> None:
     session = Session(bind=bind)
     do_upgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction
     except SQLAlchemyError as ex:
-        session.rollback()
+        session.rollback()  # pylint: disable=consider-using-transaction

Review Comment:
   **Suggestion:** In this migration, replace direct transaction control with the shared migration utility pattern from `superset.migrations.shared.utils` so behavior remains consistent across database backends. [custom_rule]
   
   **Severity Level:** Major ⚠️
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This migration performs direct Session.commit()/rollback() calls inside Alembic migration code instead of using shared migration utilities. That matches the custom rule for migration files that should rely on helpers when available.
   </details>
   <details>
   <summary><b>Rule source 📖 </b></summary>
   
   .github/copilot-instructions.md (line 294)
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=e9e0d7fde8334fe7b62cdc6eb2049e71&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=e9e0d7fde8334fe7b62cdc6eb2049e71&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py
   **Line:** 104:106
   **Comment:**
   	*Custom Rule: In this migration, replace direct transaction control with the shared migration utility pattern from `superset.migrations.shared.utils` so behavior remains consistent across database backends.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9795aa5ddb181564695c63b12ef26143a64bbdd6a696552d9445189b01dba9a3&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=9795aa5ddb181564695c63b12ef26143a64bbdd6a696552d9445189b01dba9a3&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3610464058


##########
superset/security/manager.py:
##########
@@ -4543,6 +4560,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )
+            logger.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+            if first_user:
+                self.noop_user_update(first_user)
+            return None
+        if verify_auth_db_password(user.password, password):
+            self.update_user_auth_stat(user, True)
+            return user

Review Comment:
   <!-- Bito Reply -->
   The observation is correct. Since `update_user_auth_stat` internally calls `on_user_login`, the security event and session stamping are already handled. The suggestion to add an explicit `on_user_login` call is redundant in this specific implementation.
   
   **superset/security/manager.py**
   ```
   if verify_auth_db_password(user.password, password):
               self.update_user_auth_stat(user, True)
               return user
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3612343546


##########
superset/views/users/api.py:
##########
@@ -161,6 +309,142 @@ def update_me(self) -> Response:
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
+    @expose("/password", methods=["PUT"])
+    @protect()
+    @permission_name("write")
+    @safe
+    @statsd_metrics
+    @requires_json
+    @_rate_limit_me_password_change
+    def update_my_password(self) -> Response:
+        """Update the current user's password (AUTH_DB only)
+        ---
+        put:
+          summary: Update the current user's password
+          description: >-
+            Changes the authenticated user's password when ``AUTH_TYPE`` is ``AUTH_DB``.
+            Requires the current password and a new password that satisfies
+            ``AUTH_DB_CONFIG`` policy.
+          requestBody:
+            required: true
+            content:
+              application/json:
+                schema:
+                  $ref: '#/components/schemas/CurrentUserPasswordPutSchema'
+          responses:
+            200:
+              description: Password updated successfully
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/UserResponseSchema'
+            400:
+              $ref: '#/components/responses/400'
+            401:
+              $ref: '#/components/responses/401'
+            429:
+              $ref: '#/components/responses/400'

Review Comment:
   **Suggestion:** The OpenAPI response mapping declares a 429 status but references the 400 schema, which is an incorrect contract for clients and generated SDKs. Point 429 to a proper 429 response component so tooling and UI error handling remain accurate. [docstring mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Minor 🧹</summary>
   
   ```mdx
   - ⚠️ Generated SDKs mislabel schema for 429 rate-limit responses.
   - ⚠️ API docs show incorrect reference for password rate limits.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start the Superset web application and retrieve the OpenAPI specification that includes
   `CurrentUserRestApi` from `superset/views/users/api.py:171–448` (typically served via the
   Swagger/OpenAPI UI endpoint configured in this project).
   
   2. Locate the path definition for `PUT /api/v1/me/password`, whose docstring responses
   block appears at lines 335–351 in `superset/views/users/api.py`.
   
   3. Inspect the responses section and observe that the `429` response entry (lines 348–349)
   is declared but references `#/components/responses/400` instead of a dedicated
   `#/components/responses/429` rate-limit response component.
   
   4. Use code-generation tooling or client SDKs based on this OpenAPI spec and note that
   rate-limited password-change calls (triggered by `_rate_limit_me_password_change` in the
   same file) will be modeled as returning the 400-error schema, causing an incorrect mapping
   between HTTP 429 status codes and their documented response schema.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=04c12ef8fea94cce8f5bc90fcc9cce0e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=04c12ef8fea94cce8f5bc90fcc9cce0e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/views/users/api.py
   **Line:** 348:349
   **Comment:**
   	*Docstring Mismatch: The OpenAPI response mapping declares a 429 status but references the 400 schema, which is an incorrect contract for clients and generated SDKs. Point 429 to a proper 429 response component so tooling and UI error handling remain accurate.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=06a8e97fd0ec02476cac4d606b74701f679f729405b04e10442658b056610db5&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=06a8e97fd0ec02476cac4d606b74701f679f729405b04e10442658b056610db5&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3534760167


##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,392 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+import time
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError, SQLAlchemyError
+
+from superset.extensions import db
+from superset.utils.decorators import transaction
+
+logger: logging.Logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+SESSION_AUTH_STAMP_VALIDATED_AT_KEY = "_auth_session_stamp_validated_at"
+SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY = "_auth_session_stamp_validated_db_stamp"
+
+_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
+_STAMP_CACHE_KEY_PREFIX = "auth_session_stamp:"
+_DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS = 300
+
+
+def _stamp_cache_key(user_id: int) -> str:
+    return f"{_STAMP_CACHE_KEY_PREFIX}{user_id}"
+
+
+def _get_revalidation_seconds() -> int:
+    """Return ``SESSION_AUTH_STAMP_REVALIDATION_SECONDS``, defaulting on bad config."""
+    from flask import current_app
+
+    raw = current_app.config.get("SESSION_AUTH_STAMP_REVALIDATION_SECONDS", 0)
+    try:
+        return int(raw)
+    except (TypeError, ValueError):
+        logger.warning(
+            "Invalid SESSION_AUTH_STAMP_REVALIDATION_SECONDS=%r; "
+            "defaulting to 0 (check on every request)",
+            raw,
+        )
+        return 0
+
+
+def _stamp_cache_timeout_seconds() -> int:
+    if (revalidation_seconds := _get_revalidation_seconds()) > 0:
+        return revalidation_seconds
+    return _DEFAULT_STAMP_CACHE_TIMEOUT_SECONDS
+
+
+def _get_cached_user_session_stamp(user_id: int) -> str | None:
+    try:
+        from superset.extensions import cache_manager
+
+        cached = cache_manager.cache.get(_stamp_cache_key(user_id))
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to read session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+        return None
+    return cached if isinstance(cached, str) else None
+
+
+def _cache_user_session_stamp(user_id: int, stamp: str) -> None:
+    try:
+        from superset.extensions import cache_manager
+
+        cache_manager.cache.set(
+            _stamp_cache_key(user_id),
+            stamp,
+            timeout=_stamp_cache_timeout_seconds(),
+        )
+    except Exception:  # noqa: BLE001
+        logger.debug(
+            "Unable to write session auth stamp cache for user_id=%s",
+            user_id,
+            exc_info=True,
+        )
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register login and request hooks that manage the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    from flask_login import user_logged_in
+
+    if not getattr(app, "superset_session_auth_stamp_login_connected", False):
+        app.superset_session_auth_stamp_login_connected = True
+
+        @user_logged_in.connect
+        def _sync_stamp_after_login(sender: Flask, user: Any, **extra: Any) -> None:  # noqa: ARG001
+            """Copy the DB stamp into the session after Flask-Login finalizes it."""
+            sync_session_auth_stamp_on_login(user)
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430
+        """Log out requests whose session cookie carries an outdated auth stamp."""
+        validate_session_auth_stamp_for_request()
+
+
+@transaction()
+def ensure_user_session_stamp_value(user_id: int) -> str:
+    """Return the stamp for ``user_id``, inserting a stable row if missing."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is not None:
+        return row.stamp
+    stamp = str(uuid4())
+    try:
+        with db.session.begin_nested():
+            db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=stamp))
+        return stamp
+    except IntegrityError:
+        row = db.session.get(UserSessionAuthStamp, user_id)
+        if row is None:
+            logger.exception(
+                "Failed to resolve session auth stamp after IntegrityError "
+                "for user_id=%s",
+                user_id,
+            )
+            raise
+        return row.stamp
+
+
+def clear_flask_login_remember_cookie() -> None:
+    """
+    Schedule deletion of any Flask-Login remember-me cookie on the HTTP response.
+
+    Superset does not expose remember-me in the React login flow, but Flask-Login
+    and FAB still support persistent cookies when ``remember=True``. After a
+    password change, clear any existing remember token so it cannot
+    re-establish a session without re-authentication.
+    """
+    if not has_request_context():
+        return
+    session["_remember"] = "clear"
+
+
+def sync_session_auth_stamp_on_login(user: Any) -> None:
+    """Copy the DB stamp into the signed session cookie after a successful login."""
+    if not has_request_context():
+        return
+    uid = getattr(user, "id", None)
+    if uid is None:
+        return
+    user_id = int(uid)
+    stamp = ensure_user_session_stamp_value(user_id)
+    session[SESSION_AUTH_STAMP_SESSION_KEY] = stamp
+    _mark_session_stamp_validated(stamp)
+    _cache_user_session_stamp(user_id, stamp)
+
+
+def cache_user_session_auth_stamp(user_id: int, stamp: str) -> None:
+    """Write a validated session auth stamp into the shared cache."""
+    _cache_user_session_stamp(user_id, stamp)
+
+
+@transaction()
+def bump_user_session_auth_stamp(user_id: int) -> str:
+    """Assign a new stamp so every other session for this user becomes invalid."""
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    new_stamp = str(uuid4())
+    row = db.session.get(UserSessionAuthStamp, user_id)
+    if row is None:
+        try:
+            with db.session.begin_nested():
+                db.session.add(UserSessionAuthStamp(user_id=user_id, stamp=new_stamp))
+            return new_stamp
+        except IntegrityError:
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            if row is None:
+                logger.exception(
+                    "Failed to resolve session auth stamp after IntegrityError "
+                    "for user_id=%s",
+                    user_id,
+                )
+                raise
+    row.stamp = new_stamp
+    return new_stamp
+
+
+def _resolve_stamp_check_user_id() -> int | None:
+    """Return the integer user id for the stamp check, or None to skip it."""
+    from flask import current_app
+    from flask_appbuilder.const import AUTH_DB
+    from sqlalchemy.orm.exc import DetachedInstanceError
+
+    if not has_request_context():
+        return None
+    # The stamp is only ever bumped on AUTH_DB password changes; skip the DB
+    # hit for other auth backends where the stamp never changes after login.
+    if current_app.config.get("AUTH_TYPE") != AUTH_DB:
+        return None
+    if not getattr(current_user, "is_authenticated", False):
+        return None
+    if getattr(current_user, "is_guest_user", False):
+        return None
+    try:
+        raw_id = current_user.get_id()
+    except DetachedInstanceError:
+        # The user object was detached from the session (e.g. after a rollback
+        # in a previous before_request hook). Fail open — Flask-Login will
+        # reload the user on the next request.
+        logger.warning(
+            "Skipping session auth stamp check: current_user is detached",
+            exc_info=True,
+        )
+        return None
+    if raw_id is None:
+        return None
+    try:
+        return int(raw_id)
+    except (TypeError, ValueError):
+        return None
+
+
+def _should_skip_stamp_db_lookup(
+    user_id: int, method: str, sess_stamp: str | None
+) -> bool:
+    """Return True when a recent DB check lets us skip another on read-only traffic."""
+    if method not in _SAFE_METHODS or sess_stamp is None:
+        return False
+
+    revalidation_seconds = _get_revalidation_seconds()
+    if revalidation_seconds <= 0:
+        return False
+
+    validated_at = session.get(SESSION_AUTH_STAMP_VALIDATED_AT_KEY)
+    if validated_at is None:
+        return False
+    try:
+        elapsed = time.time() - float(validated_at)
+    except (TypeError, ValueError):
+        return False
+    if elapsed >= revalidation_seconds:
+        return False
+
+    # Only skip when this session still carries the stamp we last validated and
+    # the shared cache agrees (so password rotation on another client is visible).
+    if session.get(SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY) != sess_stamp:
+        return False
+    cached_stamp = _get_cached_user_session_stamp(user_id)
+    return cached_stamp == sess_stamp
+
+
+def _mark_session_stamp_validated(db_stamp: str | None = None) -> None:
+    session[SESSION_AUTH_STAMP_VALIDATED_AT_KEY] = time.time()
+    if db_stamp is not None:
+        session[SESSION_AUTH_STAMP_VALIDATED_DB_STAMP_KEY] = db_stamp
+    session.modified = True
+
+
+def _load_db_stamp_for_user(
+    user_id: int, *, use_cache: bool = True
+) -> tuple[str | None, bool]:
+    """
+    Return ``(stamp, db_error)``.
+
+    ``db_error`` is True when the database lookup failed and the check should
+    fail open. ``stamp`` is None when no row exists.
+    """
+    from superset.models.user_session_auth_stamp import UserSessionAuthStamp
+
+    if use_cache:
+        cached_stamp = _get_cached_user_session_stamp(user_id)
+        if cached_stamp is not None:
+            return cached_stamp, False
+
+    try:
+        with db.session.begin_nested():
+            row = db.session.get(UserSessionAuthStamp, user_id)
+            db_stamp = row.stamp if row is not None else None
+    except SQLAlchemyError:
+        logger.warning(
+            "Skipping session auth stamp check due to a database error",
+            exc_info=True,
+        )
+        return None, True
+
+    if db_stamp is not None:
+        _cache_user_session_stamp(user_id, db_stamp)
+    return db_stamp, False
+
+
+def validate_session_auth_stamp_for_request() -> None:
+    """Drop login when the session cookie carries an outdated stamp for this user."""
+    from flask import request as flask_request
+
+    user_id = _resolve_stamp_check_user_id()
+    if user_id is None:
+        logger.debug(
+            "Stamp validation skipped: no user_id for %s %s",
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    sess_stamp = session.get(SESSION_AUTH_STAMP_SESSION_KEY)
+    if _should_skip_stamp_db_lookup(user_id, flask_request.method, sess_stamp):
+        logger.debug(
+            "Session auth stamp DB check skipped (recently validated) for user_id=%s "
+            "on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    db_stamp, db_error = _load_db_stamp_for_user(
+        user_id,
+        use_cache=(
+            flask_request.method in _SAFE_METHODS and _get_revalidation_seconds() > 0
+        ),
+    )
+    if db_error:
+        return
+
+    if db_stamp is None:
+        # No stamp row means the session predates stamp tracking. Allow the
+        # request without a check — sync_session_auth_stamp_on_login creates
+        # the row on every login, so this only occurs for sessions that
+        # pre-date the feature or whose row was removed independently.
+        # Committing inside a before_request hook would expire SQLAlchemy
+        # objects loaded by earlier hooks and can cause DetachedInstanceError
+        # in route handlers, so we never create the row here.
+        logger.debug(
+            "Session auth stamp check skipped: no DB stamp row for user_id=%s on %s %s",
+            user_id,
+            flask_request.method,
+            flask_request.path,
+        )
+        return
+
+    # Compare the session cookie stamp against the authoritative DB value.
+    logger.debug(
+        "Validating session stamp for user_id=%s on %s %s: sess_stamp=%s, db_stamp=%s",
+        user_id,
+        flask_request.method,
+        flask_request.path,
+        sess_stamp,
+        db_stamp,
+    )
+    if sess_stamp is None or sess_stamp != db_stamp:
+        # Sessions authenticated before stamp tracking, or a login that did not
+        # persist the stamp into the signed cookie, carry no sess stamp. Adopt
+        # the current DB value so the request can proceed; password rotation
+        # still invalidates sessions that carry an outdated non-null stamp.
+        if sess_stamp is None and db_stamp is not None:
+            session[SESSION_AUTH_STAMP_SESSION_KEY] = db_stamp
+            _mark_session_stamp_validated(db_stamp)
+            _cache_user_session_stamp(user_id, db_stamp)
+            return

Review Comment:
   **Suggestion:** This logic accepts any authenticated session that has no stamp by copying the current DB stamp into the session and allowing the request. That means stamp-less sessions (for example older sessions or flows that missed stamp write) are not invalidated after a password change, which breaks the cross-session invalidation guarantee. Treat missing session stamp as invalid once a DB stamp exists, rather than auto-adopting it. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ❌ Stamp-less sessions remain valid after password rotation.
   ⚠️ Cross-device invalidation weaker than documented guarantee.
   ⚠️ Older pre-feature sessions not forced to reauthenticate.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. A user changes their AUTH_DB password via `PUT /api/v1/me/password`, which hits
   `CurrentUserRestApi.update_my_password` at `superset/views/users/api.py:323-379`. This
   view verifies the current password, hashes the new password, and calls
   `bump_user_session_auth_stamp(g.user.id)` (imported at `superset/views/users/api.py:48`
   and implemented in `superset/utils/auth_session_stamp.py:183-204`) to rotate the per-user
   DB stamp.
   
   2. The user has another authenticated browser session whose signed cookie lacks
   `_auth_session_stamp` (for example, a session created before this feature or one that
   missed stamp persistence). This scenario is mirrored in
   `test_validate_stamp_adopts_missing_session_stamp` at
   `superset/tests/unit_tests/utils/test_auth_session_stamp.py:235-257`, where `session` has
   no `_auth_session_stamp` but the DB row’s `stamp` is set to `"db-stamp"`.
   
   3. On the next request from that stamp-less session (e.g., `GET /api/v1/me/` as in the
   tests), `validate_session_auth_stamp_for_request()` at
   `superset/utils/auth_session_stamp.py:310-392` runs via the `before_request` hook
   registered in `register_session_auth_stamp_hook()` at line 118, resolving `user_id`,
   loading `db_stamp` from `UserSessionAuthStamp`, and reading `sess_stamp =
   session.get(SESSION_AUTH_STAMP_SESSION_KEY)`, which is `None`.
   
   4. Because `sess_stamp is None or sess_stamp != db_stamp` evaluates true, the branch at
   lines 368-377 executes. Since `sess_stamp is None and db_stamp is not None`, the code
   copies `db_stamp` into the session (`SESSION_AUTH_STAMP_SESSION_KEY`), marks the stamp as
   validated, caches it, and returns without calling `logout_user()` or raising
   `Unauthorized`. As a result, this stamp-less pre-rotation session remains authenticated
   even after the password change, so some previously authenticated sessions are not
   invalidated despite the rotated DB stamp.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=99002753dc0d4ad587cb2b055a97c996&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=99002753dc0d4ad587cb2b055a97c996&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 368:377
   **Comment:**
   	*Security: This logic accepts any authenticated session that has no stamp by copying the current DB stamp into the session and allowing the request. That means stamp-less sessions (for example older sessions or flows that missed stamp write) are not invalidated after a password change, which breaks the cross-session invalidation guarantee. Treat missing session stamp as invalid once a DB stamp exists, rather than auto-adopting it.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8dc978a987390e3479d94e9a43267aaee87f4f30188bc1bb9a2e751e5b3c2a8e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8dc978a987390e3479d94e9a43267aaee87f4f30188bc1bb9a2e751e5b3c2a8e&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3534758717


##########
superset/cli/reset.py:
##########
@@ -68,7 +69,11 @@ def factory_reset(
     # Validate the user
     password = click.prompt("Admin Password", hide_input=True)
     user = security_manager.find_user(username)
-    if not user or not check_password_hash(user.password, password):
+    if (
+        not user
+        or not user.is_active
+        or not verify_auth_db_password(user.password, password)

Review Comment:
   **Suggestion:** This direct hash check bypasses the security manager authentication flow, so failed-attempt accounting/timing-balancing logic is skipped and this command behaves differently from normal login protections. Route credential validation through the security manager auth method so lockout/rate-limit and failure bookkeeping are consistently enforced. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Factory reset CLI ignores AUTH_DB login failure tracking.
   - ⚠️ Brute-force attempts via CLI bypass lockout protections.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Invoke the Superset CLI entrypoint `superset` defined in `superset/cli/main.py:55-73`,
   which auto-loads subcommands from the `superset.cli` package.
   
   2. Ensure the factory reset command is enabled by setting
   `FEATURE_FLAGS["ENABLE_FACTORY_RESET_COMMAND"] = True` so the check at
   `superset/cli/reset.py:55-57` passes.
   
   3. Run `superset factory_reset` from the CLI, enter an existing admin username and an
   incorrect password at the prompts implemented in `superset/cli/reset.py:69-70`; execution
   reaches the validation block at lines 72-76.
   
   4. In `superset/cli/reset.py:72-76`, the condition `or not
   verify_auth_db_password(user.password, password)` (line 75) directly checks the stored
   hash and, on failure, prints "Invalid credentials" and calls `sys.exit(1)` (lines 77-78)
   without ever invoking `SupersetSecurityManager.auth_user_db()` in
   `superset/security/manager.py:84-115`, so the usual failure bookkeeping via
   `update_user_auth_stat()` and fake-hash timing-balancing logic are skipped for this
   command.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=370cb74fe52e4433b8d64505913d74da&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=370cb74fe52e4433b8d64505913d74da&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/cli/reset.py
   **Line:** 75:75
   **Comment:**
   	*Security: This direct hash check bypasses the security manager authentication flow, so failed-attempt accounting/timing-balancing logic is skipped and this command behaves differently from normal login protections. Route credential validation through the security manager auth method so lockout/rate-limit and failure bookkeeping are consistently enforced.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=02b72feb614f7b5a4d8aa123552b7d05d9abeb4b33c4b2d0b80d08bd1eb58dca&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=02b72feb614f7b5a4d8aa123552b7d05d9abeb4b33c4b2d0b80d08bd1eb58dca&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4911539598

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3106962921


##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -48,19 +50,35 @@ function UserInfoModal({
     : {};
   const handleFormSubmit = async (values: FormValues) => {
     try {
-      const { confirm_password, ...payload } = values;
-      await SupersetClient.put({
-        endpoint: `/api/v1/me/`,
-        jsonPayload: { ...payload },
-      });
-      addSuccessToast(
-        isEditMode
-          ? t('The user was updated successfully')
-          : t('The password reset was successful'),
-      );
+      if (isEditMode) {
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/`,
+          jsonPayload: values,
+        });
+        addSuccessToast(t('The user was updated successfully'));
+      } else {
+        const { confirm_password, ...passwordPayload } = values;
+        void confirm_password;
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/password`,
+          jsonPayload: {
+            current_password: String(passwordPayload.current_password),
+            new_password: String(passwordPayload.new_password),
+          },
+        });
+        addSuccessToast(t('The password reset was successful'));
+      }
       onSave();
     } catch (error) {
-      addDangerToast(t('Something went wrong while saving the user info'));
+      const clientError = await getClientErrorObject(error);
+      const raw = clientError.message;
+      const text =
+        typeof raw === 'string'
+          ? raw
+          : raw && typeof raw === 'object'
+            ? (Object.values(raw).flat() as string[]).join(' ')
+            : t('Something went wrong while saving the user info');
+      addDangerToast(text);
     }

Review Comment:
   **Suggestion:** The catch block shows an error toast but does not rethrow, so `formSubmitHandler` resolves and `FormModal` treats the submission as successful, closing the modal even when the API call failed. Re-throw the error after showing the toast. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password reset modal closes after failed backend validation.
   - ❌ User info edit modal would close on update failures.
   - ⚠️ User cannot easily correct invalid current password input.
   - ⚠️ Behavior inconsistent with other FormModal-based flows.
   ```
   </details>
   
   ```suggestion
         throw error;
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the user info page rendered by `UserInfo` in
   `superset-frontend/src/pages/UserInfo/index.tsx:89-257` and click "Reset my password"
   (SubMenu button defined at `index.tsx:121-141`), which sets `modalState.resetPassword` and
   renders `<UserInfoResetPasswordModal>` at `index.tsx:233-240`.
   
   2. `UserInfoResetPasswordModal` wraps `UserInfoModal` with `isEditMode={false}`
   (`UserInfoModal.tsx:184-186`) and passes an `onSave` callback that closes the
   reset-password modal via `closeModal(ModalType.ResetPassword)` (`index.tsx:233-240`).
   
   3. In `UserInfoModal.handleFormSubmit` (`UserInfoModal.tsx:51-83`), when the user submits
   an invalid current password or the backend rejects the change, `SupersetClient.put` to
   `/api/v1/me/password` (`UserInfoModal.tsx:60-68`) rejects and control flows into the
   `catch (error)` block (`UserInfoModal.tsx:72-82), which builds an error message using
   `getClientErrorObject` and calls `addDangerToast(text);` at `UserInfoModal.tsx:81` but
   does NOT rethrow the error.
   
   4. Because the error is swallowed, the promise returned by `handleFormSubmit` resolves
   successfully, so `FormModal`'s `handleFormSubmit`
   (`packages/superset-ui-core/src/components/Modal/FormModal.tsx:56-69`) does not see an
   exception, calls `handleSave()` (`FormModal.tsx:51-54`), and invokes the parent `onSave`
   that closes the modal (`index.tsx:233-240). The user sees an error toast but the
   password-reset modal closes as if the operation succeeded, preventing immediate correction
   and diverging from other `FormModal` usages (e.g., `UserListResetPasswordModal` rethrows
   after `addDangerToast` at `features/users/UserListResetPasswordModal.tsx:63-85` so the
   modal stays open on failure).
   ```
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2082%3A82%0A%2A%2AComment%3A%2A%2A%0A%09%2APossible%20Bug%3A%20The%20catch%20block%20shows%20an%20error%20toast%20but%20does%20not%20rethrow%2C%20so%20%60formSubmitHandler%60%20resolves%20and%20%60FormModal%60%20treats%20the%20submission%20as%20successful%2C%20closing%20the%20modal%20even%20when%20the%20API%20call%20failed.%20Re-throw%20the%20error%20after%20showing%20the%20toast.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%
 2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2082%3A82%0A%2A%2AComment%3A%2A%2A%0A%09%2APossible%20Bug%3A%20The%20catch%20block%20shows%20an%20error%20toast%20but%20does%20not%20rethrow%2C%20so%20%60formSubmitHandler%60%20resolves%20and%20%60FormModal%60%20treats%20the%20submission%20as%20successful%2C%20closing%20the%20modal%20even%20when%20the%20API%20call%20failed.%20Re-throw%20the%20error%20after%20showing%20the%20toast.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 82:82
   **Comment:**
   	*Possible Bug: The catch block shows an error toast but does not rethrow, so `formSubmitHandler` resolves and `FormModal` treats the submission as successful, closing the modal even when the API call failed. Re-throw the error after showing the toast.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=a631068aa9274ce8b861563424918de15e6402398dfa10982d8a34c2fd25eb33&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=a631068aa9274ce8b861563424918de15e6402398dfa10982d8a34c2fd25eb33&reaction=dislike'>👎</a>



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -48,19 +50,35 @@ function UserInfoModal({
     : {};
   const handleFormSubmit = async (values: FormValues) => {
     try {
-      const { confirm_password, ...payload } = values;
-      await SupersetClient.put({
-        endpoint: `/api/v1/me/`,
-        jsonPayload: { ...payload },
-      });
-      addSuccessToast(
-        isEditMode
-          ? t('The user was updated successfully')
-          : t('The password reset was successful'),
-      );
+      if (isEditMode) {
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/`,
+          jsonPayload: values,
+        });
+        addSuccessToast(t('The user was updated successfully'));
+      } else {
+        const { confirm_password, ...passwordPayload } = values;
+        void confirm_password;
+        await SupersetClient.put({
+          endpoint: `/api/v1/me/password`,
+          jsonPayload: {
+            current_password: String(passwordPayload.current_password),
+            new_password: String(passwordPayload.new_password),
+          },
+        });
+        addSuccessToast(t('The password reset was successful'));
+      }
       onSave();

Review Comment:
   **Suggestion:** `onSave` is called inside the submit handler, but `FormModal` already calls `onSave` after `formSubmitHandler` resolves. This triggers duplicate save side effects (double modal close/refresh/toasts) on successful submissions. Remove the manual call and let `FormModal` handle it once. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Minor 🧹</summary>
   
   ```mdx
   - ⚠️ User profile refresh API called twice after edits.
   - ⚠️ Modal close logic run twice per successful submission.
   - ⚠️ Extra duplicate side effects on future onSave handlers here.
   ```
   </details>
   
   ```suggestion
         // `FormModal` triggers `onSave` after a successful `formSubmitHandler`.
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the user info page rendered by `UserInfo` in
   `superset-frontend/src/pages/UserInfo/index.tsx:89-257` and click "Edit user" (SubMenu
   button defined at `index.tsx:142-160`), which sets `modalState.edit` and renders
   `<UserInfoEditModal>` at `index.tsx:243-251`.
   
   2. `UserInfoEditModal` is a wrapper around `UserInfoModal`
   (`superset-frontend/src/features/userInfo/UserInfoModal.tsx:188-190`), passing an `onSave`
   prop that calls `closeModal(ModalType.Edit)` and `getUserDetails()` (`index.tsx:243-249`).
   
   3. In `UserInfoModal` (`UserInfoModal.tsx:33-83`), submitting the form invokes
   `FormModal`'s internal `handleFormSubmit`
   (`packages/superset-ui-core/src/components/Modal/FormModal.tsx:56-69`), which does `await
   formSubmitHandler(values);` then calls `handleSave()` that in turn calls the parent
   `onSave()` (`FormModal.tsx:51-54`).
   
   4. `formSubmitHandler` is `handleFormSubmit` defined in `UserInfoModal.tsx:51-83`; on
   successful API calls it executes `onSave();` directly at `UserInfoModal.tsx:71`. As a
   result, on a successful edit the parent `onSave` (which refreshes user details and closes
   the modal at `index.tsx:243-249`) is invoked once from `UserInfoModal` and a second time
   from `FormModal`, causing duplicate refresh requests to `/api/v1/me/`
   (`index.tsx:102-110`) and redundant close side effects.
   ```
   </details>
   
   [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2071%3A71%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20%60onSave%60%20is%20called%20inside%20the%20submit%20handler%2C%20but%20%60FormModal%60%20already%20calls%20%60onSave%60%20after%20%60formSubmitHandler%60%20resolves.%20This%20triggers%20duplicate%20save%20side%20effects%20%28double%20modal%20close%2Frefresh%2Ftoasts%29%20on%20successful%20submissions.%20Remove%20the%20manual%20call%20and%20let%20%60FormModal%60%20handle%20it%20once.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%
 20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset-frontend%2Fsrc%2Ffeatures%2FuserInfo%2FUserInfoModal.tsx%0A%2A%2ALine%3A%2A%2A%2071%3A71%0A%2A%2AComment%3A%2A%2A%0A%09%2ALogic%20Error%3A%20%60onSave%60%20is%20called%20inside%20the%20submit%20handler%2C%20but%20%60FormModal%60%20already%20calls%20%60onSave%60%20after%20%60formSubmitHandler%60%20resolves.%20This%20triggers%20duplicate%20save%20side%20effects%20%28double%20modal%20close%2Frefresh%2Ftoasts%29%20on%20successful%20submissions.%20Remove%20the%20manual%20call%20and%20let%20%60FormModal%60%20handle%20it%20once.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/features/userInfo/UserInfoModal.tsx
   **Line:** 71:71
   **Comment:**
   	*Logic Error: `onSave` is called inside the submit handler, but `FormModal` already calls `onSave` after `formSubmitHandler` resolves. This triggers duplicate save side effects (double modal close/refresh/toasts) on successful submissions. Remove the manual call and let `FormModal` handle it once.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=a0af1a63bd34b62bd1e62cf5c04f8913bfd4d56a4f0901aa20b9afceaa44e7c6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=a0af1a63bd34b62bd1e62cf5c04f8913bfd4d56a4f0901aa20b9afceaa44e7c6&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3540985965


##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,104 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+from re import Pattern
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import Argon2Error
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE: Pattern[str] = re.compile(r"^\$2[aby]\$\d{2}\$")
+_ARGON2_HASH_PREFIX: str = "$argon2"
+
+_argon2_hasher: PasswordHasher = PasswordHasher()
+
+
+def is_bcrypt_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the bcrypt wire format."""
+    return bool(_BCRYPT_HASH_RE.match(password_hash))
+
+
+def is_argon2_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the argon2 wire format."""
+    return password_hash.startswith(_ARGON2_HASH_PREFIX)
+
+
+def hash_auth_db_password(password: str, algorithm: str | None = None) -> str:
+    """
+    Hash a plaintext password for AUTH_DB storage.
+
+    Uses ``AUTH_DB_CONFIG["password_hash_algorithm"]`` when ``algorithm`` is omitted.
+    """
+    resolved: str = algorithm or get_auth_db_password_hash_algorithm()
+    if resolved == "argon2":
+        return _argon2_hasher.hash(password)
+    if resolved == "bcrypt":
+        try:
+            encoded: bytes = password.encode("utf-8")
+        except UnicodeEncodeError as exc:
+            raise ValueError("Password cannot be encoded as UTF-8.") from exc
+        if len(encoded) > BCRYPT_MAX_PASSWORD_BYTES:
+            raise ValueError(
+                f"Password exceeds bcrypt's {BCRYPT_MAX_PASSWORD_BYTES}-byte limit."
+            )
+        return bcrypt.hashpw(encoded, bcrypt.gensalt()).decode("utf-8")
+    raise ValueError(f"Unsupported AUTH_DB hash algorithm: {resolved}")

Review Comment:
   **Suggestion:** The explicit `algorithm` argument is used verbatim, unlike config-derived values which are normalized in `get_auth_db_password_hash_algorithm`; passing values like `"BCRYPT"` or `" argon2 "` will incorrectly raise `ValueError` at runtime. Normalize `algorithm` with `strip().lower()` before comparison so direct callers get the same contract as config-based resolution. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Direct hash_auth_db_password callers passing uppercase algorithm crash.
   - ⚠️ Inconsistent behavior versus AUTH_DB_CONFIG normalization causes confusion.
   - ⚠️ Future password utilities relying on explicit algorithms become fragile.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Locate the hash helper `hash_auth_db_password` in
   `superset/utils/auth_db_password_hash.py:50-69`, which accepts an optional `algorithm`
   parameter and sets `resolved: str = algorithm or get_auth_db_password_hash_algorithm()`.
   
   2. Inspect the configuration resolver `get_auth_db_password_hash_algorithm` in
   `superset/utils/auth_db_password.py:15-37`, which normalizes config values with `algorithm
   = str(raw_algorithm).strip().lower()` before validating against
   `_SUPPORTED_HASH_ALGORITHMS`.
   
   3. Note that `hash_auth_db_password` does not normalize its explicit `algorithm` argument;
   it only compares `resolved` directly to `"argon2"` and `"bcrypt"` (lines 57-59 in
   `auth_db_password_hash.py`).
   
   4. In any calling code (for example a new helper or script) invoke
   `hash_auth_db_password("secret", "BCRYPT")` or `hash_auth_db_password("secret", " argon2
   ")`; since `resolved` becomes `"BCRYPT"` / `" argon2 "`, neither `if resolved == "argon2"`
   nor `if resolved == "bcrypt"` matches, and the function raises `ValueError("Unsupported
   AUTH_DB hash algorithm: BCRYPT")` at line 69 instead of hashing successfully, unlike the
   normalized config-driven path.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b3a1c4f9faf14977be57d53b87ca4e72&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b3a1c4f9faf14977be57d53b87ca4e72&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 56:69
   **Comment:**
   	*Api Mismatch: The explicit `algorithm` argument is used verbatim, unlike config-derived values which are normalized in `get_auth_db_password_hash_algorithm`; passing values like `"BCRYPT"` or `" argon2 "` will incorrectly raise `ValueError` at runtime. Normalize `algorithm` with `strip().lower()` before comparison so direct callers get the same contract as config-based resolution.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=e915c54b94a2adff98a69feb02e151e4891b46b26eb8970e43b3454cb630b9ca&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=e915c54b94a2adff98a69feb02e151e4891b46b26eb8970e43b3454cb630b9ca&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3602396077


##########
superset/utils/auth_db_password.py:
##########
@@ -0,0 +1,265 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Password policy helpers for AUTH_DB (database authentication)."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any
+
+from flask import current_app as app
+from marshmallow import ValidationError
+
+# Defaults are merged with ``AUTH_DB_CONFIG`` from Flask config (partial overrides).
+AUTH_DB_DEFAULTS: dict[str, Any] = {
+    "password_min_length": 12,
+    "password_require_uppercase": True,
+    "password_require_lowercase": True,
+    "password_require_digit": True,
+    "password_require_special": True,
+    "password_common_list_check": True,
+    "password_hash_algorithm": "bcrypt",
+    "reset_token_expiry_minutes": 30,
+    "reset_rate_limit": "5 per 15 minutes",
+    "login_rate_limit": "10 per 5 minutes",
+    "login_max_failures": 5,
+    "login_lockout_duration_minutes": 15,
+}
+
+_PUBLIC_PASSWORD_POLICY_KEYS: tuple[str, ...] = (
+    "password_min_length",
+    "password_require_uppercase",
+    "password_require_lowercase",
+    "password_require_digit",
+    "password_require_special",
+    "password_common_list_check",
+)
+
+_SUPPORTED_HASH_ALGORITHMS: frozenset[str] = frozenset({"bcrypt", "argon2"})
+
+# bcrypt silently truncates input beyond this UTF-8 byte limit.
+BCRYPT_MAX_PASSWORD_BYTES: int = 72
+
+_COMMON_PASSWORDS: frozenset[str] = frozenset(
+    password.lower()
+    for password in {
+        "password",
+        "password1",
+        "password123",
+        "123456",
+        "12345678",
+        "123456789",
+        "qwerty",
+        "abc123",
+        "monkey",
+        "letmein",
+        "trustno1",
+        "dragon",
+        "baseball",
+        "iloveyou",
+        "master",
+        "sunshine",
+        "ashley",
+        "bailey",
+        "shadow",
+        "superman",
+        "qazwsx",
+        "michael",
+        "football",
+        "welcome",
+        "jesus",
+        "ninja",
+        "mustang",
+        "password1!",
+        "admin",
+        "admin123",
+        "root",
+        "toor",
+        "guest",
+        "p@ssw0rd",
+        "Passw0rd",
+        "Password1",
+        "Password123",
+        "Welcome1",
+        "Qwerty123",
+    }
+)
+
+
+def get_merged_auth_db_config() -> dict[str, Any]:
+    """Return ``AUTH_DB_DEFAULTS`` merged with ``AUTH_DB_CONFIG`` from app config."""
+    overrides: Any = app.config.get("AUTH_DB_CONFIG") or {}
+    if not isinstance(overrides, Mapping):
+        raise ValidationError(
+            {
+                "AUTH_DB_CONFIG": [
+                    "Invalid AUTH_DB_CONFIG. Expected a mapping of policy options."
+                ]
+            }
+        )
+    return {**AUTH_DB_DEFAULTS, **overrides}
+
+
+def get_auth_db_login_rate_limit_string() -> str:
+    """
+    Return the configured AUTH_DB ``login_rate_limit`` string for Flask-Limiter.
+
+    Used for endpoints that verify a password (for example ``PUT /api/v1/me/password``).
+    """
+    return str(
+        get_merged_auth_db_config().get(
+            "login_rate_limit", AUTH_DB_DEFAULTS["login_rate_limit"]
+        )
+    )

Review Comment:
   **Suggestion:** This coerces any configured `login_rate_limit` value to a string, which turns invalid values like `None` into the literal string `"None"` and can break Flask-Limiter parsing at request time (causing endpoint failures instead of a controlled fallback). Validate the value and fall back to the default when it is missing or malformed instead of unconditional `str(...)` coercion. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password change endpoint 500s when login_rate_limit misconfigured.
   - ⚠️ Misconfigured limiter string blocks self-service password changes.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Enable request rate limiting so the password-change endpoint uses Flask-Limiter by
   setting `RATELIMIT_ENABLED = True` as defined in `superset/config.py:12-15`, and ensure a
   `limiter` instance is attached to `security_manager` (checked in
   `_rate_limit_me_password_change` at `superset/views/users/api.py:82-86`).
   
   2. Configure an invalid AUTH_DB login rate limit by setting
   `app.config["AUTH_DB_CONFIG"]["login_rate_limit"] = None` (or another non-string/invalid
   object); `get_merged_auth_db_config` in `superset/utils/auth_db_password.py:104-115`
   merges this value without validating the `login_rate_limit` field.
   
   3. Issue a `PUT /api/v1/me/password` request, which is handled by
   `CurrentUserRestApi.update_my_password` decorated with `@_rate_limit_me_password_change`
   at `superset/views/users/api.py:310-319`; inside the wrapper,
   `limiter.limit(get_auth_db_login_rate_limit_string(),
   key_func=_me_password_rate_limit_key, methods=["PUT"])` is called at
   `superset/views/users/api.py:28-32`.
   
   4. `get_auth_db_login_rate_limit_string` at `superset/utils/auth_db_password.py:118-128`
   coerces the invalid `login_rate_limit` value to the literal string `"None"` via
   `str(...)`; when Flask-Limiter attempts to parse this string as a rate-limit expression,
   it raises an exception, causing the password-change request to fail with an internal error
   instead of applying a safe default limit or allowing the request.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1aae2ec480114ca191b86912761f674d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1aae2ec480114ca191b86912761f674d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password.py
   **Line:** 124:128
   **Comment:**
   	*Logic Error: This coerces any configured `login_rate_limit` value to a string, which turns invalid values like `None` into the literal string `"None"` and can break Flask-Limiter parsing at request time (causing endpoint failures instead of a controlled fallback). Validate the value and fall back to the default when it is missing or malformed instead of unconditional `str(...)` coercion.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=453a9edf52035629c05e0400cac6c27deb8f4b10fc0acea18d0229289fcea865&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=453a9edf52035629c05e0400cac6c27deb8f4b10fc0acea18d0229289fcea865&reaction=dislike'>👎</a>



##########
superset/security/manager.py:
##########
@@ -4799,6 +4844,39 @@ def is_admin(self) -> bool:
             role.name for role in self.get_user_roles()
         ]
 
+    def auth_user_db(self, username: str, password: str) -> User | None:
+        """
+        Authenticate a database user, verifying bcrypt/argon2 and legacy hashes.
+        """
+        if username is None or username == "":
+            return None
+        first_user = self.get_first_user()
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        else:
+            # Balance failure and success
+            _ = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                current_app.config.get(
+                    "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
+                    DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK,
+                ),
+                "password",
+            )

Review Comment:
   **Suggestion:** The fake password check for unknown/inactive users always uses a Werkzeug PBKDF2 hash, while real users are now verified with bcrypt/argon2 via `verify_auth_db_password`; this creates a measurable timing difference between “user exists” and “user does not exist” paths and reintroduces username-enumeration risk. Use a fake hash generated with the same configured `AUTH_DB` algorithm (or route both paths through the same verifier) so failed-login timing stays consistent. [security]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ AUTH_DB login exposes username existence via timing side-channel.
   - ⚠️ Facilitates remote enumeration of valid AUTH_DB user accounts.
   - ⚠️ Undermines protection provided by fake password hash checks.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Observe how the security manager is wired into Superset:
   `superset/initialization/__init__.py:1209-1218` sets `appbuilder.security_manager_class =
   SupersetSecurityManager`, and `SupersetSecurityManager.register_views` in
   `superset/security/manager.py:4880-4887` registers `SupersetAuthView` from
   `superset/views/auth.py:33-42` as the `/login` view, so database logins ultimately
   authenticate through this manager.
   
   2. Inspect `SupersetSecurityManager.auth_user_db` in
   `superset/security/manager.py:4847-4879`: when `user is None or (not user.is_active)` at
   line 4861, the code executes the fake password check at lines 4863-4868, calling
   `werkzeug.security.check_password_hash` with
   `current_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"]` (falling back to
   `DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK`, a PBKDF2 hash defined at lines 125-132); when
   a user exists, line 4874 instead calls `verify_auth_db_password(user.password, password)`.
   
   3. Inspect the real verifier and password algorithm defaults: `verify_auth_db_password` in
   `superset/utils/auth_db_password_hash.py:72-103` uses bcrypt (`bcrypt.checkpw`) when the
   stored hash matches the bcrypt format and argon2 (`argon2.PasswordHasher.verify`) when it
   starts with `"$argon2"`, only falling back to Werkzeug `check_password_hash` for legacy
   hashes; `superset/utils/auth_db_password.py:28-37` and `144-166` show that
   `AUTH_DB_CONFIG["password_hash_algorithm"]` defaults to `"bcrypt"`, so newly created
   AUTH_DB users will typically have bcrypt hashes.
   
   4. Run Superset with `AUTH_TYPE = AUTH_DB` and default `AUTH_DB_CONFIG`, create an active
   AUTH_DB user `alice` (whose password hash will be bcrypt by default), then from an HTTP
   client issue repeated login attempts: (a) POST to `/login` as `alice` with an incorrect
   password, which drives the login stack to call `auth_user_db` and execute the
   bcrypt/argon2 verification path via `verify_auth_db_password`; (b) POST to `/login` with a
   non-existent username such as `ghost` and any password, which drives `auth_user_db` into
   the `user is None` branch and executes only the PBKDF2 fake hash check at
   `superset/security/manager.py:4863-4868`. Measuring average response times for case (a) vs
   case (b) over many requests will reveal a timing difference attributable to the different
   hash algorithms/parameters on these two failure paths, enabling remote username
   enumeration despite the intended fake-check mitigation.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=62579314660542fc88f5b19f8153b83a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=62579314660542fc88f5b19f8153b83a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/manager.py
   **Line:** 4863:4868
   **Comment:**
   	*Security: The fake password check for unknown/inactive users always uses a Werkzeug PBKDF2 hash, while real users are now verified with bcrypt/argon2 via `verify_auth_db_password`; this creates a measurable timing difference between “user exists” and “user does not exist” paths and reintroduces username-enumeration risk. Use a fake hash generated with the same configured `AUTH_DB` algorithm (or route both paths through the same verifier) so failed-login timing stays consistent.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=743abf14a41e927a7d0d8328854a00ffccb60a125bd204b54d95b717c2175222&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=743abf14a41e927a7d0d8328854a00ffccb60a125bd204b54d95b717c2175222&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3528690352


##########
superset/security/manager.py:
##########
@@ -118,6 +121,15 @@
 
 logger = logging.getLogger(__name__)
 
+# Fallback werkzeug-format hash used to balance timing on failed logins when
+# ``AUTH_DB_FAKE_PASSWORD_HASH_CHECK`` is not provided by the Flask-AppBuilder
+# config defaults. Mirrors FAB's pbkdf2 default so ``check_password_hash`` does
+# comparable work whether or not the target user exists.
+DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK = (
+    "pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"  # noqa: S105
+    "c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c"
+)

Review Comment:
   **Suggestion:** Add an explicit type annotation to this newly introduced module-level constant so it complies with the required type-hinting rule for relevant variables. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This newly added module-level constant is annotated nowhere, and the rule requires type hints for relevant variables that can be annotated. A string constant like this can be typed as `str`, so the suggestion identifies a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=18bccd0469ca40658b4c80f3f1219309&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=18bccd0469ca40658b4c80f3f1219309&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/manager.py
   **Line:** 128:131
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this newly introduced module-level constant so it complies with the required type-hinting rule for relevant variables.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5f2e7cbe6aac90745ca588cf614ab2c6a37c1438c2fc4d17c1b67621ae031219&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=5f2e7cbe6aac90745ca588cf614ab2c6a37c1438c2fc4d17c1b67621ae031219&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3528736057


##########
superset/security/manager.py:
##########
@@ -118,6 +121,15 @@
 
 logger = logging.getLogger(__name__)
 
+# Fallback werkzeug-format hash used to balance timing on failed logins when
+# ``AUTH_DB_FAKE_PASSWORD_HASH_CHECK`` is not provided by the Flask-AppBuilder
+# config defaults. Mirrors FAB's pbkdf2 default so ``check_password_hash`` does
+# comparable work whether or not the target user exists.
+DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK = (
+    "pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"  # noqa: S105
+    "c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c"
+)

Review Comment:
   **Suggestion:** The fallback fake hash is hardcoded to PBKDF2, so failed-login timing no longer matches real verification when users are stored with `argon2` (or non-PBKDF2 hashes). This creates a measurable username-enumeration side channel because non-existent users always take PBKDF2 cost while existing users take the configured hash cost. Use a fake hash that matches the configured AUTH_DB hash algorithm (and update it when the configured algorithm changes) so missing-user and existing-user failures do comparable work. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Login endpoint leaks AUTH_DB user existence via timing.
   - ⚠️ Weakens resistance to username enumeration across AUTH_DB logins.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Configure AUTH_DB to use argon2 by setting ``AUTH_DB_CONFIG["password_hash_algorithm"]
   = "argon2"``, which `get_auth_db_password_hash_algorithm` reads in
   `superset/utils/auth_db_password.py:144-166` to drive hashing via `hash_auth_db_password`
   in `superset/utils/auth_db_password_hash.py:50-59`.
   
   2. Create or migrate an AUTH_DB user so their `User.password` field contains an argon2
   hash produced by `hash_auth_db_password` (argon2 branch at
   `superset/utils/auth_db_password_hash.py:56-59`), ensuring login verification uses argon2
   for that account.
   
   3. Send login requests to the `/login` route exposed by `SupersetAuthView` in
   `superset/views/auth.py:33-40`; Flask-AppBuilder’s `AuthView` (external dependency) uses
   the configured `SupersetSecurityManager.auth_user_db` method
   (`superset/security/manager.py:29-60`) to authenticate AUTH_DB users.
   
   4. For a non-existent or inactive username, `auth_user_db` follows the `user is None or
   (not user.is_active)` branch at `superset/security/manager.py:42-54`, invoking
   `check_password_hash` against the PBKDF2 constant
   `DEFAULT_AUTH_DB_FAKE_PASSWORD_HASH_CHECK` defined at
   `superset/security/manager.py:128-131`; for an existing argon2 user with a wrong password,
   it calls `verify_auth_db_password` at `superset/security/manager.py:55`, which executes
   the argon2 verifier path in `superset/utils/auth_db_password_hash.py:89-93`. The
   difference between PBKDF2 work for missing users and argon2 work for existing users
   produces measurably different response times for failed logins, allowing an attacker to
   infer whether a given username exists.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1db56d634c1b4c8bb4101cca1a3d6ac0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1db56d634c1b4c8bb4101cca1a3d6ac0&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/security/manager.py
   **Line:** 128:131
   **Comment:**
   	*Security: The fallback fake hash is hardcoded to PBKDF2, so failed-login timing no longer matches real verification when users are stored with `argon2` (or non-PBKDF2 hashes). This creates a measurable username-enumeration side channel because non-existent users always take PBKDF2 cost while existing users take the configured hash cost. Use a fake hash that matches the configured AUTH_DB hash algorithm (and update it when the configured algorithm changes) so missing-user and existing-user failures do comparable work.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b6ad35f63e5d57b8a55ec8cd0fd83c7eed8e9dddee98dadf7e00dc4a787dc88e&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b6ad35f63e5d57b8a55ec8cd0fd83c7eed8e9dddee98dadf7e00dc4a787dc88e&reaction=dislike'>👎</a>



##########
superset/utils/auth_db_password_hash.py:
##########
@@ -0,0 +1,98 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Hash and verify AUTH_DB passwords using bcrypt or argon2."""
+
+from __future__ import annotations
+
+import re
+from re import Pattern
+
+import bcrypt
+from argon2 import PasswordHasher
+from argon2.exceptions import Argon2Error
+from werkzeug.security import check_password_hash
+
+from superset.utils.auth_db_password import (
+    BCRYPT_MAX_PASSWORD_BYTES,
+    get_auth_db_password_hash_algorithm,
+)
+
+_BCRYPT_HASH_RE: Pattern[str] = re.compile(r"^\$2[aby]\$\d{2}\$")
+_ARGON2_HASH_PREFIX: str = "$argon2"
+
+_argon2_hasher: PasswordHasher = PasswordHasher()
+
+
+def is_bcrypt_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the bcrypt wire format."""
+    return bool(_BCRYPT_HASH_RE.match(password_hash))
+
+
+def is_argon2_password_hash(password_hash: str) -> bool:
+    """Return True when ``password_hash`` uses the argon2 wire format."""
+    return password_hash.startswith(_ARGON2_HASH_PREFIX)
+
+
+def hash_auth_db_password(password: str, algorithm: str | None = None) -> str:
+    """
+    Hash a plaintext password for AUTH_DB storage.
+
+    Uses ``AUTH_DB_CONFIG["password_hash_algorithm"]`` when ``algorithm`` is omitted.
+    """
+    resolved: str = algorithm or get_auth_db_password_hash_algorithm()
+    if resolved == "argon2":
+        return _argon2_hasher.hash(password)
+    if resolved == "bcrypt":
+        encoded: bytes = password.encode("utf-8")
+        if len(encoded) > BCRYPT_MAX_PASSWORD_BYTES:
+            raise ValueError(
+                f"Password exceeds bcrypt's {BCRYPT_MAX_PASSWORD_BYTES}-byte limit."
+            )
+        return bcrypt.hashpw(encoded, bcrypt.gensalt()).decode("utf-8")

Review Comment:
   **Suggestion:** The bcrypt hashing branch encodes raw input with UTF-8 without handling encoding failures, so malformed Unicode passwords can raise `UnicodeEncodeError` and break password-change requests with a 500. Catch `UnicodeEncodeError` here and convert it to a controlled validation failure. [type error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Password change API may 500 on malformed password.
   - ⚠️ Future bcrypt utility callers brittle to encoding issues.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In a Python shell, import `hash_auth_db_password` from
   `superset/utils/auth_db_password_hash.py:50-66` and ensure
   `AUTH_DB_CONFIG["password_hash_algorithm"]` is left at the default `"bcrypt"` (resolved
   via `get_auth_db_password_hash_algorithm` at
   `superset/utils/auth_db_password.py:144-166`).
   
   2. Construct an invalid Unicode `str` such as `bad_password = "\udcff"` and call
   `hash_auth_db_password(bad_password)` to exercise the bcrypt branch.
   
   3. Execution reaches the bcrypt block at `superset/utils/auth_db_password_hash.py:59-65`;
   `bad_password.encode("utf-8")` on line 60 raises `UnicodeEncodeError` before any
   controlled `ValueError` or hash is produced, and this exception is not handled inside
   `hash_auth_db_password`.
   
   4. In the password-change API handler at `superset/views/users/api.py:378`,
   `hash_auth_db_password(body["new_password"])` is called inside a `try` that only catches
   `marshmallow.ValidationError`, so the same malformed input passed through
   `_load_password_change_body` and `validate_auth_db_password` results in an unhandled
   exception and HTTP 500 for `PUT /api/v1/me/password` instead of a structured validation
   failure.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=1cce00d9f73847798a9bd13385879507&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=1cce00d9f73847798a9bd13385879507&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_db_password_hash.py
   **Line:** 59:65
   **Comment:**
   	*Type Error: The bcrypt hashing branch encodes raw input with UTF-8 without handling encoding failures, so malformed Unicode passwords can raise `UnicodeEncodeError` and break password-change requests with a 500. Catch `UnicodeEncodeError` here and convert it to a controlled validation failure.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b07406b7f30e04248397580e171eb895aad976ebe753983c7c56bba8c4d3fadb&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=b07406b7f30e04248397580e171eb895aad976ebe753983c7c56bba8c4d3fadb&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "dpgaspar (via GitHub)" <gi...@apache.org>.
dpgaspar commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3428227121


##########
superset/migrations/versions/2026-06-16_16-00_33a0aac0a26a_merging_two_heads.py:
##########
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""merging two heads
+
+Revision ID: 33a0aac0a26a
+Revises: ('c6219cac9270', '78a40c08b4be')

Review Comment:
   Why do we need this?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3467929151


##########
superset-frontend/src/utils/generateAuthDbPassword.ts:
##########
@@ -0,0 +1,287 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { t } from '@apache-superset/core/translation';
+/**
+ * Client-side generator aligned with default ``AUTH_DB_CONFIG`` / Python
+ * ``superset.utils.auth_db_password`` (minimum length, character classes, common list).
+ * Keep ``AUTH_DB_COMMON_PASSWORDS`` in sync with ``_COMMON_PASSWORDS`` in that module.
+ */
+export const AUTH_DB_PASSWORD_MIN_LENGTH = 12;
+export interface AuthDbPasswordPolicy {
+  password_min_length: number;
+  password_require_uppercase: boolean;
+  password_require_lowercase: boolean;
+  password_require_digit: boolean;
+  password_require_special: boolean;
+  password_common_list_check: boolean;
+}
+
+export const AUTH_DB_DEFAULT_PASSWORD_POLICY: AuthDbPasswordPolicy = {
+  password_min_length: AUTH_DB_PASSWORD_MIN_LENGTH,
+  password_require_uppercase: true,
+  password_require_lowercase: true,
+  password_require_digit: true,
+  password_require_special: true,
+  password_common_list_check: true,
+};
+
+/**
+ * Lowercased entries; keep in sync with ``_COMMON_PASSWORDS`` in
+ * ``superset/utils/auth_db_password.py``. The sync is enforced automatically by
+ * ``generateAuthDbPassword.test.ts``, which parses the Python source.
+ */
+export const AUTH_DB_COMMON_PASSWORDS = new Set(
+  [
+    'password',
+    'password1',
+    'password123',
+    '123456',
+    '12345678',
+    '123456789',
+    'qwerty',
+    'abc123',
+    'monkey',
+    'letmein',
+    'trustno1',
+    'dragon',
+    'baseball',
+    'iloveyou',
+    'master',
+    'sunshine',
+    'ashley',
+    'bailey',
+    'shadow',
+    'superman',
+    'qazwsx',
+    'michael',
+    'football',
+    'welcome',
+    'jesus',
+    'ninja',
+    'mustang',
+    'password1!',
+    'admin',
+    'admin123',
+    'root',
+    'toor',
+    'guest',
+    'p@ssw0rd',
+    'Passw0rd',
+    'Password1',
+    'Password123',
+    'Welcome1',
+    'Qwerty123',
+  ].map(s => s.toLowerCase()),
+);
+
+const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+const LOWER = 'abcdefghijkmnopqrstuvwxyz';
+const DIGIT = '23456789';
+const SPECIAL = '!@#$%^&*-_=+';
+const ALPHANUM = UPPER + LOWER + DIGIT;
+
+export interface AuthDbPasswordPolicyChecks {
+  minLength: boolean;
+  uppercase: boolean;
+  lowercase: boolean;
+  digit: boolean;
+  special: boolean;
+  commonPassword: boolean;
+}
+
+function getCodePointLength(text: string): number {
+  return Array.from(text).length;
+}
+
+/** Mirrors ``int(cfg.get("password_min_length", default))`` in auth_db_password.py. */
+function resolveAuthDbPasswordMinLength(
+  passwordMinLength: number | undefined | null,
+): number {
+  if (passwordMinLength === undefined || passwordMinLength === null) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  const parsed = Number(passwordMinLength);
+  if (!Number.isFinite(parsed)) {
+    return AUTH_DB_PASSWORD_MIN_LENGTH;
+  }
+  return Math.trunc(parsed);
+}
+
+function secureRandomInt(maxExclusive: number): number {
+  if (maxExclusive <= 0) {
+    throw new Error('secureRandomInt: maxExclusive must be positive');
+  }
+  const cryptoObj = globalThis.crypto;
+  if (!cryptoObj?.getRandomValues) {
+    throw new Error(
+      'secureRandomInt: a Web Crypto implementation with getRandomValues ' +
+        'is required to generate passwords',
+    );
+  }
+  const maxUint32 = 0xffffffff;
+  const limit = maxUint32 - (maxUint32 % maxExclusive);
+  const buf = new Uint32Array(1);
+  let value: number;
+  do {
+    cryptoObj.getRandomValues(buf);
+    value = buf[0]!;
+  } while (value >= limit);
+  return value % maxExclusive;
+}
+
+function pick(pool: string): string {
+  return pool[secureRandomInt(pool.length)]!;
+}
+
+function shuffleInPlace(chars: string[]): void {
+  for (let i = chars.length - 1; i > 0; i -= 1) {
+    const j = secureRandomInt(i + 1);
+    const t = chars[i]!;
+    chars[i] = chars[j]!;
+    chars[j] = t;
+  }
+}
+
+function getRequiredCharacterPools(policy: AuthDbPasswordPolicy): string[] {
+  const pools: string[] = [];
+  if (policy.password_require_uppercase) {
+    pools.push(UPPER);
+  }
+  if (policy.password_require_lowercase) {
+    pools.push(LOWER);
+  }
+  if (policy.password_require_digit) {
+    pools.push(DIGIT);
+  }
+  if (policy.password_require_special) {
+    pools.push(SPECIAL);
+  }
+  return pools;
+}
+
+function getGenerationPool(policy: AuthDbPasswordPolicy): string {
+  let pool = '';
+  if (policy.password_require_uppercase) {
+    pool += UPPER;
+  }
+  if (policy.password_require_lowercase) {
+    pool += LOWER;
+  }
+  if (policy.password_require_digit) {
+    pool += DIGIT;
+  }
+  if (policy.password_require_special) {
+    pool += SPECIAL;
+  }
+  return pool || ALPHANUM + SPECIAL;
+}
+
+/** Returns rule-by-rule checks for default AUTH_DB password policy. */
+export function getAuthDbPasswordPolicyChecks(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): AuthDbPasswordPolicyChecks {
+  const minLength = resolveAuthDbPasswordMinLength(policy.password_min_length);
+  return {
+    minLength: getCodePointLength(password) >= minLength,
+    uppercase: !policy.password_require_uppercase || /[A-Z]/.test(password),
+    lowercase: !policy.password_require_lowercase || /[a-z]/.test(password),
+    digit: !policy.password_require_digit || /\p{Nd}/u.test(password),
+    special:
+      !policy.password_require_special || /[^\p{L}\p{N}\s]/u.test(password),
+    commonPassword:
+      !policy.password_common_list_check ||
+      !AUTH_DB_COMMON_PASSWORDS.has(password.toLowerCase().trim()),
+  };
+}
+
+function satisfiesAuthDbPasswordPolicy(
+  password: string,
+  policy: AuthDbPasswordPolicy,
+): boolean {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  return Object.values(checks).every(Boolean);
+}
+
+/** True when the string satisfies default AUTH_DB rules (mirrors backend checks). */
+export function satisfiesDefaultAuthDbPasswordPolicy(
+  password: string,
+): boolean {
+  return satisfiesAuthDbPasswordPolicy(
+    password,
+    AUTH_DB_DEFAULT_PASSWORD_POLICY,
+  );
+}
+
+/** Returns the first validation error for a password under the given policy. */
+export function getAuthDbPasswordPolicyError(
+  password: string,
+  policy: AuthDbPasswordPolicy = AUTH_DB_DEFAULT_PASSWORD_POLICY,
+): string | null {
+  const checks = getAuthDbPasswordPolicyChecks(password, policy);
+  if (!checks.minLength) {
+    return t(
+      'Password must be at least %s characters long.',
+      resolveAuthDbPasswordMinLength(policy.password_min_length),
+    );
+  }
+  if (policy.password_require_uppercase && !checks.uppercase) {
+    return t('Password must contain at least one uppercase letter.');
+  }
+  if (policy.password_require_lowercase && !checks.lowercase) {
+    return t('Password must contain at least one lowercase letter.');
+  }
+  if (policy.password_require_digit && !checks.digit) {
+    return t('Password must contain at least one digit.');
+  }
+  if (policy.password_require_special && !checks.special) {
+    return t(
+      'Password must contain at least one special character (not a letter, digit, or space).',
+    );
+  }
+  if (policy.password_common_list_check && !checks.commonPassword) {
+    return t('Password is too common.');
+  }
+  return null;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Inconsistent error message text</b></div>
   <div id="fix">
   
   The error message for the common-password policy check is shorter than the Python backend's message ('Password is too common.' vs 'This password is too common. Choose a stronger password.'). Users see inconsistent feedback depending on whether validation happens client-side or server-side. Align the frontend message with the backend.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
       );
     }
     if (policy.password_common_list_check && !checks.commonPassword) {
       return t('This password is too common. Choose a stronger password.');
     }
     return null;
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #8a0589</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/features/userInfo/UserInfoModal.tsx:
##########
@@ -135,15 +252,20 @@ function UserInfoModal({
       requiredFields={requiredFields}
       initialValues={initialValues}
     >
-      {isEditMode ? <EditModeFields /> : <ResetPasswordFields />}
+      {(form: FormInstance) =>
+        isEditMode ? <EditModeFields /> : <ResetPasswordFields form={form} />
+      }
     </FormModal>
   );
 }
 
-export const UserInfoResetPasswordModal = (
+export const ChangePasswordModal = (
   props: Omit<UserInfoModalProps, 'isEditMode' | 'user'>,
 ) => <UserInfoModal {...props} isEditMode={false} />;
 
+// Backward-compatible alias for existing imports.
+export const UserInfoResetPasswordModal = ChangePasswordModal;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Unused export alias</b></div>
   <div id="fix">
   
   The `UserInfoResetPasswordModal` alias has no callers in the codebase (grep confirmed across all `.tsx`/`.ts` source files). Keeping it introduces semantic dead code and creates maintenance risk — future refactors targeting 'reset password' behavior may erroneously skip this export.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #8a0589</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4727505707

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4852316438

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4845505469

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-5006181510

   Thanks @shantanukhond!
   
   CI's red right now (pre-commit plus the mysql/postgres/sqlite jobs), so that's step one. There's also a good number of unresolved bot findings on this one, and a handful read like real security concerns rather than nits. 
   
   The timing differences in the failed-login path and the `session.commit()` calls inside the migration breaking Alembic's transaction boundary both stood out. Given this touches password hashing and session invalidation directly, I'd want those actually resolved (and tested) before we dig into review, not just acknowledged.
   
   EnxDev's question about remember-me sessions adopting the DB stamp without a fresh login also seems worth an answer. That reads like it could be a real auth bypass.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "EnxDev (via GitHub)" <gi...@apache.org>.
EnxDev commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4993683213

   EnxDev's Review Agent — apache/superset#39469 · HEAD 2ec20b8
   
   request changes — Pillar 1 is now substantively delivered and the split was honored, but the new migration forks the Alembic tree (again) and CI is red at this HEAD.
   
   Supersedes my review at f0de405. Both blockers raised there are addressed in spirit: the plaintext-password logging is fixed (manual _log_audit_event with an id-only payload, guarded by test_put_my_password_does_not_log_passwords on success and failure paths), and the migration was re-pointed — but at a revision that has since gained another child on master, so the tree forks again.
   
   Pillar 1 status vs. the SIP (your May 3 checklist)
   
   | SIP Item | Status |
   |----------|--------|
   | **Multi-session invalidation** | ✅ `user_session_auth_stamp` + `before_request` check implemented. Integration test verifies that a cloned session cookie receives `401` (one gap remains; see findings below). |
   | **Remember-me token revocation** | ⚠️ Cookie is cleared only on the browser where the password is changed. See the stamp-adoption finding below. |
   | **Password confirmation field** | ✅ Implemented with `validates_schema`, UI field, and integration test. |
   | **Real-time strength / policy indicator** | ✅ Implemented via `GET /me/password/policy` and `AuthDbPasswordPolicyIndicator`. |
   | **Audit metadata (self/admin, source IP)** | ❌ Not implemented (see finding below). |
   | **`password_hash_algorithm` (bcrypt/argon2)** | ✅ Implemented, including legacy hash fallback, bcrypt 72-byte handling, and test coverage. |
   
   Split status: ✅ admin-reset UI/API and auth_audit_log are out of the diff (existing Log reused per dpgaspar). Prior blockers verified fixed at this HEAD: doubled UserLoggedIn audits, cache-before-commit race, session-clear-before-commit, CSRF rotation via SupersetClient.reAuthenticate().
   
   🔴 Functional
   
   - superset/migrations/versions/2026-05-13_12-00_c6219cac9270_add_user_session_auth_stamp_table.py:31 · High — down_revision = "3a8e6f2c1b95" collides with master: b1c2d3e4f5a6 (add_subjects_tables) already claims that same parent, so the merged tree has two heads and superset db upgrade aborts — today's test-load-examples failure is exactly Error: Multiple head revisions are present… at superset init. Third round of this failure mode (b4a3f2e1d0c9 → 9e1f3b8c4d2a → 3a8e6f2c1b95): hand-picking a revision goes stale as master moves. Rebase onto current master and set down_revision from superset db heads at rebase time, in the same push. (Note: the "Check DB migration conflict" workflow passing is meaningless — it only posts courtesy comments, it never runs alembic heads.)
   
   🟡 Should-fix
   
   - superset/views/users/api.py:400 + superset/security/manager.py:1339 — the audit-metadata SIP item is still open. UserPasswordChanged carries only {"user_id"} — no self/admin flag, no source IP (the Log row has no IP column and the payload doesn't include one). Worse, the admin path (reset_password, still reachable via FAB's Reset Password action) emits no password-change audit event at all. Since Log is being reused, put it in the JSON payload — {"user_id", "initiated_by": "self"|"admin", "actor_id", "source_ip"} — and emit the same event from the reset_password admin branch.
   - superset/utils/auth_session_stamp.py:363 — a session with no stamp in the cookie adopts the current DB stamp instead of being invalidated. That covers pre-upgrade sessions (defensible rollout window) but also sessions re-established from a Flask-Login remember cookie after a password change — remember processing sets _user_id without running on_user_login, so a stolen remember token survives rotation in deployments that enable remember-me (clear_flask_login_remember_cookie:139 only clears the changing browser's cookie). Suggest adopting only when session.get("_fresh") is truthy, or documenting the limitation.
   - superset/security/manager.py — pre-commit is red: ruff I001, the added LOGMSG_WAR_SEC_LOGIN_FAILED import is unsorted. One-line fix; include in the rebase push.
   
   🔵 Nits
   
   - superset/views/users/api.py:292 — "password" in request.json raises TypeError (→ 500) when the JSON body is a scalar (5, true); guard with isinstance(request.json, dict).
   - superset/security/manager.py:4862 — the timing-balance fake check runs werkzeug pbkdf2 while real users verify bcrypt/argon2, so unknown-username failures are measurably faster — a username-enumeration timing oracle. Hash the dummy with the configured algorithm.
   
   🙌 Praise
   
   - superset/views/users/api.py:369 — the optimistic lock (filter(User.password == old_hash)) with post-commit-only cache write closes both prior races cleanly, and the conflict/500 paths now have tests.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4994456810

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3596015301


##########
superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py:
##########
@@ -101,9 +101,9 @@ def upgrade() -> None:
     session = Session(bind=bind)
     do_upgrade(session)
     try:
-        session.commit()
+        session.commit()  # pylint: disable=consider-using-transaction

Review Comment:
   **Suggestion:** Calling `session.commit()` inside an Alembic migration breaks the outer Alembic-managed transaction boundary. If a failure happens after this commit (for example while stamping migration state), the permission changes can be permanently committed while the revision is not fully recorded, leaving migration state inconsistent on reruns. Keep this work inside Alembic’s transaction and flush the session instead of committing here. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Migration data committed while Alembic version not recorded.
   - ⚠️ Rerunning migrations sees inconsistent permissions versus revision.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. From a Superset instance using this PR code, run `superset db upgrade` so Alembic
   executes migrations via `superset/migrations/env.py:64-84`, where
   `context.run_migrations()` is wrapped inside `context.begin_transaction()`.
   
   2. During the run, Alembic loads revision module
   `superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py`
   and calls its `upgrade()` function defined around line 99 in the PR hunk.
   
   3. Inside `upgrade()`, `Session(bind=op.get_bind())` is created, `do_upgrade(session)`
   mutates permission rows, and `session.commit()` at line 104 commits those changes directly
   on the Alembic-managed connection, independently of Alembic’s outer transaction in
   `env.py:82`.
   
   4. Introduce a later migration that intentionally raises an exception after this revision
   (for example, a failing DDL in another script), rerun `superset db upgrade`, and observe
   that Alembic aborts and may not fully record the revision chain while the permission
   updates committed by `upgrade()` remain persisted, demonstrating desynchronized data
   versus migration version tracking.
   ```
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=c29c0acdb3734f718b560003b706e33c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=c29c0acdb3734f718b560003b706e33c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/migrations/versions/2026-06-23_03-23_a7d3f1b9c2e4_cleanup_stale_can_import_pvm.py
   **Line:** 104:104
   **Comment:**
   	*Logic Error: Calling `session.commit()` inside an Alembic migration breaks the outer Alembic-managed transaction boundary. If a failure happens after this commit (for example while stamping migration state), the permission changes can be permanently committed while the revision is not fully recorded, leaving migration state inconsistent on reruns. Keep this work inside Alembic’s transaction and flush the session instead of committing here.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=406d3b1f01c02afd44f6b79954a7efdd9332128f62e1269201ce67ca38e44f27&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=406d3b1f01c02afd44f6b79954a7efdd9332128f62e1269201ce67ca38e44f27&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #39469:
URL: https://github.com/apache/superset/pull/39469#issuecomment-4781318137

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(auth): implement password change API for AUTH_DB authentication [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3453906021


##########
tests/integration_tests/users/api_tests.py:
##########
@@ -19,18 +19,31 @@
 
 from unittest.mock import patch
 
+from flask_appbuilder.const import AUTH_OAUTH
+
 from superset import security_manager
+from superset.extensions import db
+from superset.utils.auth_db_password import get_public_auth_db_password_policy
+from superset.utils.auth_db_password_hash import hash_auth_db_password
 from superset.utils import json, slack  # noqa: F401
-from tests.conftest import with_config
-from tests.integration_tests.base_tests import SupersetTestCase
+from tests.integration_tests.base_tests import DEFAULT_PASSWORD, SupersetTestCase
 from tests.integration_tests.conftest import with_feature_flags
 from tests.integration_tests.constants import ADMIN_USERNAME
+from tests.integration_tests.test_app import app as superset_integration_app
 
 meUri = "/api/v1/me/"  # noqa: N816
+mePasswordUri = "/api/v1/me/password"  # noqa: N816
 AVATAR_URL = "/internal/avatar.png"
 
 
 class TestCurrentUserApi(SupersetTestCase):
+    def _restore_admin_default_password(self, app=None) -> None:

Review Comment:
   **Suggestion:** Add an explicit type annotation for the `app` parameter (for example, a Flask app type or an optional variant) so the helper method is fully typed. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added Python helper method, and its `app` parameter is untyped.
   The custom rule requires new or modified Python functions/methods to be fully typed, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=16623d39320f4508acbdb508908743af&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=16623d39320f4508acbdb508908743af&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 40:40
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for the `app` parameter (for example, a Flask app type or an optional variant) so the helper method is fully typed.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=67a1e17dec108d0c0b98186cd33509e0197574e78e90542fe21b92c3d487d0a1&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=67a1e17dec108d0c0b98186cd33509e0197574e78e90542fe21b92c3d487d0a1&reaction=dislike'>👎</a>



##########
superset/utils/auth_session_stamp.py:
##########
@@ -0,0 +1,154 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Session stamp: invalidate all browser sessions when a user's password changes."""
+
+from __future__ import annotations
+
+import logging
+from typing import Any
+from uuid import uuid4
+
+from flask import Flask, has_request_context, session
+from flask_login import current_user, logout_user
+from sqlalchemy.exc import IntegrityError
+
+from superset.extensions import db
+
+logger = logging.getLogger(__name__)
+
+SESSION_AUTH_STAMP_SESSION_KEY = "_auth_session_stamp"
+
+
+def register_session_auth_stamp_hook(app: Flask) -> None:
+    """Register a before_request handler that enforces the per-user session stamp."""
+    if getattr(app, "superset_session_auth_stamp_hook_registered", False):
+        return
+    app.superset_session_auth_stamp_hook_registered = True
+
+    @app.before_request
+    def _validate_user_session_auth_stamp() -> None:  # noqa: WPS430

Review Comment:
   **Suggestion:** Add an inline docstring to the newly added nested request hook function so it complies with the requirement that new functions are documented. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The nested request-hook function is newly introduced and has no docstring in the final file state. The custom rule requires newly added Python functions to be documented inline, so this is a real violation.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=6733555a05914ffc85a8c62efe1c08b2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=6733555a05914ffc85a8c62efe1c08b2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/auth_session_stamp.py
   **Line:** 42:43
   **Comment:**
   	*Custom Rule: Add an inline docstring to the newly added nested request hook function so it complies with the requirement that new functions are documented.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8109ca5695e558ec5daa657fae9cb73bc6f5794e7f0c0c6c750a7cf9bba7ed5a&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8109ca5695e558ec5daa657fae9cb73bc6f5794e7f0c0c6c750a7cf9bba7ed5a&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +113,214 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self):

Review Comment:
   **Suggestion:** Add an explicit `-> None` return type annotation to this new test method to satisfy the full typing requirement for new Python code. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is a newly added test method and it omits an explicit return annotation.
   Under the rule for new Python code, it should be fully typed, so the issue is real.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=a65f4dcd1038452f868b3e0f7bf0e712&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=a65f4dcd1038452f868b3e0f7bf0e712&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 116:116
   **Comment:**
   	*Custom Rule: Add an explicit `-> None` return type annotation to this new test method to satisfy the full typing requirement for new Python code.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=aa06020ce8fd2cccd3d7a84523b30635c5db6a5096862bc9a2959c04de318771&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=aa06020ce8fd2cccd3d7a84523b30635c5db6a5096862bc9a2959c04de318771&reaction=dislike'>👎</a>



##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +113,214 @@ def test_update_me_empty_payload(self):
         rv = self.client.put("/api/v1/me/", json={})
         assert rv.status_code == 400
 
+    def test_update_me_rejects_password_when_auth_db(self):
+        self.login(ADMIN_USERNAME)
+        rv = self.client.put(meUri, json={"password": "ignored"})
+        assert rv.status_code == 400
+        data = json.loads(rv.data.decode("utf-8"))
+        assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+    def test_put_my_password_wrong_current(self):

Review Comment:
   **Suggestion:** Add an explicit `-> None` return type annotation to this new test method to keep newly introduced methods fully typed. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   This is another newly added test method without a return type annotation.
   Since the surrounding code is being changed, the custom rule requires it to be fully typed.
   </details>
   
   [![Fix in Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=c81c6a0cfd664bce8cad294fc4b492b6&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [![Fix in VSCode Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=c81c6a0cfd664bce8cad294fc4b492b6&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/integration_tests/users/api_tests.py
   **Line:** 123:123
   **Comment:**
   	*Custom Rule: Add an explicit `-> None` return type annotation to this new test method to keep newly introduced methods fully typed.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=cf24be972741261b29e4f8fff438f78311eab492f925415e9a2ef45fe24048b1&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=cf24be972741261b29e4f8fff438f78311eab492f925415e9a2ef45fe24048b1&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org