Skip to content

spring: Add Flask password-sync utility (1/4 split of #168) - #169

Closed
RudraBJoshi wants to merge 2 commits into
Open-Coding-Society:masterfrom
CSA-Admin-OCS:split/spring/s1-flask-password-sync
Closed

spring: Add Flask password-sync utility (1/4 split of #168)#169
RudraBJoshi wants to merge 2 commits into
Open-Coding-Society:masterfrom
CSA-Admin-OCS:split/spring/s1-flask-password-sync

Conversation

@RudraBJoshi

Copy link
Copy Markdown

Splitting #168 into smaller, independently-reviewable PRs across spring/flask/pages. This one covers password database syncing.

Adds FlaskPasswordSync, a standalone utility that calls Flask's POST /api/internal/sync-password with a shared secret, so a Spring-side reset can keep Flask's password in sync. Not wired up to any caller yet — that happens in the next PR in this stack, which depends on this class existing.

Stack: this PR → "Add OAuth + student ID verified password reset flow" and "Refuse to sync a plaintext password to Flask..." both branch from here.

Original PR: #168

FlaskPasswordSync calls Flask's internal sync endpoint
(POST /api/internal/sync-password) with a shared secret
(INTERNAL_SYNC_KEY), so a password reset completed on Spring also lands
on the Flask account for the same uid. Not wired up to any caller yet --
that's the OAuth-verified reset flow, next PR in the stack. Best-effort:
a sync failure is logged, not fatal to whatever already-successful
operation triggered it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ensures any Claude Code session working in this repo automatically sees
that .clauderc (one directory up, at the prodsys root) has the project
structure, ongoing work, deploy requirements, and known gaps -- CLAUDE.md
is auto-loaded into context at session start, .clauderc on its own is not.
@jm1021

jm1021 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

This should be tied to Profile Password reset

@jm1021

jm1021 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

The process from the logged-in Reset Password should use this utility.

IF Login -> Profile/Reset Password button -> Delete all Cookies -> Reset Flask -> Reset Spring (if the Spring account does not exist, create it) -> Force Logout -> Return to the login page.

IF Logout -> we need to put them through an identification process, probably by sending them an email. On the email confirmation link, you can reset their Flask password to a temporary password. The next login should force them to reset their password. I don't believe we want to do more than reset Flask as we may have several needs for recovery and we should have that sequence in one place.

RudraBJoshi added a commit to CSA-Admin-OCS/spring that referenced this pull request Aug 31, 2026
Addresses jm1021's review feedback on PR Open-Coding-Society#169/flask Open-Coding-Society#74 ("tied to
Profile Password reset") reinterpreted for the current architecture:
the profile page's direct password field was removed in a later PR in
the same stack in favor of routing everyone through this OAuth wizard,
so "tied to profile reset" now means "tied to the wizard's completion".

Track MVC logins in a SessionRegistry (tracking only, no session cap)
and force-expire a uid's sessions after /reset/oauth/complete succeeds,
closing the previously-documented gap where the JWT path invalidated on
password change but the MVC HttpSession path didn't.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@RudraBJoshi

Copy link
Copy Markdown
Author

Addressed in split/spring/s2-oauth-verified-reset (spring #170): reinterpreted for the current architecture since the profile page's own direct password-change field was removed in a later PR (pages #1371) in favor of routing everyone -- logged in or not -- through the same OAuth-verified reset wizard. /reset/oauth/complete now force-expires the account's MVC sessions via a new SessionRegistry-backed invalidateActiveSessions, and the frontend explicitly calls Spring's /logout on success to clear that browser's cookies too, closing the gap where a password reset invalidated JWTs but not the session-based admin-portal login. The 'reset Flask first, create Spring if missing' direction doesn't map onto the current design -- the OAuth flow requires a uid's Spring account to already exist just to authenticate the request, so there's no path where that could trigger. Full writeup in spring/docs/forgot-password-pipeline.md under 'MVC HttpSession invalidation.'

@jm1021

jm1021 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This has a hard-coded localhost address that will not work in production. String flaskUri = resolve("FLASK_URI", "http://localhost:8587"); Traditionally, we change passwords from Frontend user input. In that case, we obtain the URI from the frontend.

import { pythonURI, javaURI, fetchOptions } from '{{ site.baseurl }}/assets/js/api/config.js';

Is this a case where we change the password in the Spring Admin UI and want it updated in Flask? This would be a very low-volume use case. In my view, Flask is in charge, so I do not want the Spring --> Flask path.


Synchronization and Recovery Practices

The primary principle for this system is:

Flask is the System of Record for user identity. Spring is a synchronized replica and should not independently become the source of truth. Flask validation establishes the minimum compatibility standard for synchronized identity data. Therefore, Flask validation rules must meet or exceed the requirements of Spring and any downstream system. Data accepted by Flask should be valid for synchronization without requiring Spring to reject, modify, or reinterpret the master record.

For that reason, synchronization should be coordinated through the existing frontend API paths:

Frontend → Flask — obtain the authoritative state
Frontend → Spring — apply the required synchronized state

Flask remains the System of Record; the frontend is the mechanism through which the user performs synchronization. This avoids creating a new privileged Flask → Spring or Spring → Flask communication path.

1. User Changes Password in the Frontend

When a user changes their password through the frontend:

  1. The frontend submits the password change to Flask.
  2. Flask performs validation and updates the master account record.
  3. After Flask successfully completes the update, the frontend initiates the corresponding Spring update using the existing authenticated API path.

The frontend should not retrieve password hashes, encrypted passwords, or authentication tokens from Flask for the purpose of sending them to Spring.

The browser should not be responsible for synchronizing sensitive credentials between backends.

2. User Account Synchronization or Recovery

There may be situations where a Spring account is missing or out of synchronization.

In these cases:

  1. Flask remains the source of truth.
  2. The frontend obtains the authoritative account information from Flask through an authorized API and then submits the required synchronization data to Spring through the existing authenticated API path.
  3. Spring should either create the missing account or update the existing account based on the Flask record.

Synchronization may include fields such as:

  • UID
  • GitHub ID
  • Name
  • Student ID
  • Other approved identity fields required by Spring

The synchronization process should be designed so that it is safe to run repeatedly. If the Spring record already exists, it should be updated rather than duplicated.

Flask data determines the desired state. Spring is brought into alignment with that state.

3. Forgot Password or UID

The password recovery process should remain centralized.

A user requests account recovery through the frontend.

The system should:

  1. Identify the account using approved recovery information.
  2. Send an email containing the appropriate account recovery information.
  3. Provide a secure link back to the frontend.
  4. Require the user to establish a new password through the normal Flask-controlled password update process.
  5. Synchronize Spring only after Flask successfully completes the password change.

The password reset process should never depend upon manually retrieving or transferring password hashes through the frontend.

4. Administrative Reset and Cleanup

Administrative tools in Jinja2 or Thymeleaf may need to support account recovery, cleanup, or removal. Worst case scenario.

Examples may include:

  • Resetting an account
  • Removing an account
  • Recreating a missing Spring account
  • Repairing synchronization
  • Initiating a user password recovery process

The goal should be to develop safe administrative recovery operations rather than allowing Flask and Spring administrators to directly overwrite each other's data.

Administrative Rule

Flask owns the account. Spring reflects the account. Recovery operations restore Spring to the Flask-defined state. Options avoid creating new update paths or security rules (ie nginx preflight, in app CORS)

This provides a clear recovery path when synchronization fails and prevents the system from developing two competing sources of truth.

Admin
  │
  ▼
Flask Jinja2 Admin
  │
  │ Modify master record
  ▼
Flask
  │
  │ sync_required = true
  ▼
User logs in
  │
  ▼
Frontend detects synchronization required
  │
  ▼
Temporary Synchronization Mode
  │
  ├── Normal application access: BLOCKED
  │
  └── Synchronization: ALLOWED
          │
          ▼
     Read Flask master
          │
          ▼
     User clicks Synchronize
          │
          ├──► Flask: validate/read master
          │
          └──► Spring: create/update replica
                    │
                    ▼
              Verify success
                    │
                    ▼
          Flask: sync_required = false
                    │
                    ▼
              Normal access

@jm1021

jm1021 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

After verbal confirmation, my inclination is that we will close this PR.

@jm1021

jm1021 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

This is banned from design shared above.

@jm1021 jm1021 closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants