Skip to content

Consolidate the adopted course account as the single website identity #100

Description

@alexeygrigorev

Parent epics: #5, #7, #10

Related implementation owners: #52, #54, #60, #61, and #97

Owner decision and outcome

The account adopted with the Course Management Platform is the single durable website account for learners and human staff. Evolve accounts.CustomUser and its existing relations in place; do not introduce a parallel website user, login, profile, session, or social-account identity.

A returning course learner or staff member must retain the same durable account and be able to authenticate through the preserved login methods after consolidation. The migration retains existing account primary keys where records are not duplicates and retains an explicit old-ID-to-survivor mapping for every reconciled duplicate. Existing enrollments, submissions, reviews, scores, certificates, account profile fields, preferences, social connections, staff links, and audit references continue to resolve to that account.

This decision does not make a GitHub-owned public Person profile an authentication record. Person remains the canonical public editorial identity from #3; an account may be explicitly linked to a Person without granting permissions or duplicating either record.

Product and architecture authority

Preserved identity contract

The consolidation starts from the adopted account schema and behavior, including:

  • accounts.CustomUser as AUTH_USER_MODEL, preserving the existing app/model/table and migration identity;
  • existing primary key, username/login identifier, normalized email evidence, password hash where supported, last_login, date_joined, active/staff/superuser state, and current account role fields;
  • certificate name, country/region, registration role, About me, GitHub/LinkedIn/personal website URLs, dark mode, preferred timezone, email preferences, and course-specific public-display preferences;
  • Django Allauth EmailAddress and SocialAccount/provider links and the currently supported learner login behavior;
  • Django session ownership and security semantics, protected-route next behavior, logout, and account settings;
  • account foreign keys from enrollments, submissions, reviews, votes, complaints, scores, certificates, tokens/principals, staff assignments, audit records, and other adopted course data.

The consolidated application must have one authoritative user lookup and one authenticated request.user. New public, learner, course, and Studio surfaces consume that identity and the same session rather than copying account fields into another website profile.

Scope

In-place account consolidation

  • Inventory the adopted user model, authentication backends/providers, Allauth email/social records, session backend/cookie behavior, login/logout/settings routes, user foreign keys, compatibility APIs, and account navigation before changing them.
  • Evolve the adopted model through expand-and-contract migrations. Do not replace it with a greenfield user table or renumber clean source accounts.
  • Make verified normalized email the learner account identity without losing legacy username/login compatibility during the migration window.
  • Preserve existing login provider connections and callback behavior. A provider callback for an existing person must resolve the existing account, never create a second website account merely because the visitor arrived from a new site surface.
  • Keep current authenticated sessions valid through an ordinary in-place release. Consolidation must not perform a blanket session flush. Legacy-host compatibility sessions remain valid while those routes are served.
  • Produce and test a secure cross-host continuity plan for the eventual courses.datatalks.clubdatatalks.club transition. It may not broaden cookie scope without security approval or expose handoff credentials in URLs, logs, analytics, screenshots, or referrers. Replay, expiry, CSRF/fixation, logout, revocation, and next handling must be explicit. If browser cookie boundaries require visible reauthentication, it must be a reviewed migration exception: the login returns to the intended route and resolves the same account ID, never a new account.

Duplicate and conflict reconciliation

  • A snapshot-pinned, idempotent dry run groups candidate duplicates using normalized email, Allauth verified-email evidence, provider plus provider UID, username/login identifiers, and account relations. It reports exact source IDs and counts without logging raw provider payloads, tokens, or unrelated PII.
  • Generate a reviewed source-account → survivor mapping. Existing distinct accounts are never merged solely because a social provider supplied an unverified email or because one record has the newest last_login.
  • Ambiguous ownership, conflicting provider UIDs, or irreconcilable identifiers fail closed into a quarantine/review report. They do not create an account, attach a provider, or select the “most recent” user automatically.
  • Preserve the selected survivor's primary key. Every absorbed source ID remains recorded in a durable alias/migration map so imports, audit evidence, compatibility APIs, and rollback can resolve it.
  • Reparent all account-owned relationships exactly once and reconcile totals before an absorbed record can be disabled. Course work from multiple legitimate source records is retained and cannot cross-link to another person's account.
  • Privilege is never gained by union: is_staff, superuser status, groups, course/cohort capabilities, API credentials, and public Person links require their existing authoritative evidence or separate reviewed grant. Duplicate reconciliation cannot turn a learner into staff.
  • Consent is never inferred or widened. Profile/preference conflicts use documented per-field rules, choose the privacy-preserving state where applicable, and surface unexplained differences instead of silently overwriting them.
  • Social connections move only with verified ownership evidence. Password hashes and recovery/email-change state retain safe Django semantics; affected tokens/sessions are invalidated only when required by a documented security condition.

Shared website account navigation

  • The adopted Course Management Platform shell is the single authentication-aware shell for new website pages, including the bounded P0: Build a fully navigable Django review skeleton #97 review surfaces.
  • Signed-out navigation exposes one same-host login destination. Signed-in navigation exposes the signed-in account, account settings, learner/course destinations, and one logout action. Staff-only destinations are shown through the shared capability policy, not by introducing another staff login or public-Person inference.
  • Login, logout, settings, social-connection, callback, and protected-route links preserve safe next behavior and never bounce to a second account system or legacy login page.
  • Desktop and mobile navigation are keyboard complete, expose state accessibly, avoid horizontal overflow, and do not publicly cache authenticated content.

Anonymized review/test content

  • Content-only local imports and the P0: Build a fully navigable Django review skeleton #97 representative release create no real CustomUser, EmailAddress, SocialAccount, session, provider credential, enrollment, or staff record.
  • Deterministic local/browser fixtures use synthetic non-routable identities and contain no production email, account ID, social UID, access token, registration data, or profile PII.
  • GitHub Person projections used by content review remain public content records and do not silently create/link authenticated accounts.

Compatibility, API, and observability

  • Existing course HTML and authenticated API behavior keeps using the adopted account through the compatibility window. New learner and management APIs reference the same durable account identity; no endpoint gets a website-only user table.
  • Existing tokens/principals are inventoried and mapped to their owner without exposing plaintext. Their hardening/rotation remains coordinated with Complete production admin API identity and credential lifecycle controls #33/Replace global course staff access and plaintext API tokens with scoped capabilities #52.
  • Authentication, linking, duplicate review, merge, disablement, session handoff, and rollback outcomes emit redacted audit events. Raw OAuth/social payloads, email addresses, password material, session keys, cookies, tokens, and handoff credentials never enter logs or issue/test artifacts.
  • Metrics distinguish successful returning login, explicit reauthentication, account-link conflict, unexpected new-account creation, session/auth failure, and rollback without using PII labels.

Non-goals

Migration and rollback contract

  1. Pin the source snapshot and run a no-write inventory covering every account, email/social/login relation, session/auth contract, dependent table, and duplicate/conflict cluster.
  2. Add nullable mapping/alias/reconciliation state and any normalized identity constraints without dropping old columns, identifiers, or rows.
  3. Dry-run the reviewed mapping repeatedly; report source/target counts, unchanged IDs, duplicate groups, ambiguous groups, per-table relationship totals/checksums, field decisions, and rejected rows.
  4. Apply survivor mappings and dependent-row changes idempotently with outbound email and provider side effects disabled. Keep absorbed account rows and compatibility identifiers available but unable to authenticate twice after verified consolidation.
  5. Validate login, provider linking, sessions, account settings, staff capability lookup, course workflows, APIs, and audit references on a production-like copy before enabling the new resolver/navigation in production.
  6. Cut over behind an observable feature/configuration boundary, run returning-account and duplicate canaries, and retain the old resolver plus non-destructive mapping data for the rollback window.
  7. Contract old identifiers/rows only after Preserve legacy course HTML/API compatibility and rehearse full data migration #60's final rehearsal, production verification, backup/restore evidence, and rollback window. Deletion/anonymization follows the privacy process, not ordinary migration cleanup.

Rollback uses an immutable application revision that can still read the expanded schema. It never reverses successful relationship moves destructively, re-enables two independently authenticating accounts for one person, discards post-cutover writes, or clears all sessions. Workers/provider callbacks are paused or pinned as needed, alias mappings remain available, and any authentication-failure threshold is a release rollback trigger under spec 09.

Acceptance criteria

  • accounts.CustomUser remains the one AUTH_USER_MODEL; all site, course, learner, Studio, compatibility, and API authentication resolves one durable account with no parallel website user/profile/session model.
  • The account inventory classifies every identity field, login/provider route, session behavior, dependent foreign key, compatibility identifier, and current account navigation action before migration.
  • Clean source accounts retain their primary IDs and preserved fields/relations; every reconciled source ID has a durable reviewed survivor mapping and no account-owned educational or audit record is lost or attached to the wrong person.
  • Verified normalized-email identity and legacy login compatibility are introduced through forward/backward-tested expand-and-contract migrations without renumbering or recreating the adopted user table.
  • Duplicate email, case variant, username/email overlap, multiple EmailAddress, conflicting social UID, staff/learner collision, and incomplete-profile cases produce deterministic reviewed results; ambiguous cases fail closed and newest-last_login is not an automatic authority.
  • Existing supported learner login methods, provider connections, callback outcomes, account settings/profile/social links/preferences, logout, safe next, and recovery/change behavior remain characterized and pass for returning accounts.
  • An ordinary consolidation release preserves valid existing sessions and does not globally log users out. Legacy-host and eventual cross-host continuity/explicit-reauth behavior passes the approved fixation, replay, expiry, revocation, logout, cache, and credential-leak tests while always returning the same account ID.
  • Duplicate reconciliation cannot grant staff/superuser/group/course capability, infer consent, expose another user's data, or create a new social link without verified ownership.
  • Every new website surface renders the one shared signed-out login or signed-in account navigation at desktop/mobile; protected actions use the same session and account, and staff links use capability policy rather than a second login.
  • P0: Build a fully navigable Django review skeleton #97 content-only projections and all local review fixtures remain anonymized and create no account/social/session/registration rows or production identifiers.
  • Existing course learner and authenticated API characterization suites pass against the consolidated account; compatibility and new APIs agree on account identity and denial behavior.
  • The migration is snapshot-pinned, dry-runnable, idempotent, redacted, count/checksum reconciled, and safe to rerun; outbound email/provider side effects remain disabled during rehearsal/import.
  • Production-like rehearsal and rollback prove account/login/session continuity, alias resolution, no unexplained relationship-count difference, no lost post-cutover write, no double-authenticating duplicate, and no destructive reversal.
  • Logs, metrics, audit events, migration reports, screenshots, and issue comments contain no raw OAuth payload, production email/profile PII, password/session/token/cookie/handoff secret, or provider credential.

Django and integration scenarios

  1. Single identity: authenticate through each currently supported login path, visit public content, account settings, course dashboard, and an authorized Studio route, and assert one request.user.pk, session owner, and account record throughout.
  2. Returning learner: import a source learner with profile/preferences, social connection, enrollment, submission, review, score, and certificate; log in after consolidation and verify the same account ID and all records/fields.
  3. Returning staff: retain the source staff account ID and safe session behavior; prove capabilities are resolved separately, a public Person link grants nothing, and future Integrate staff OIDC/MFA login, session lifecycle, and break-glass recovery #61 OIDC attaches to the same account.
  4. Duplicate matrix: exercise case/whitespace email variants, username-email overlap, one/multiple verified emails, conflicting provider UID, two active learners with course history, learner/staff collision, and missing email. Verify reviewed survivor/alias or fail-closed quarantine with no privilege/consent union.
  5. Social linking: returning Google/GitHub/Slack fixtures connect only after verified ownership, replay idempotently, and reject unverified/conflicting claims without selecting the most recent user or logging provider payloads.
  6. Session lifecycle: preserve a valid pre-release session through the in-place migration; test fixation, idle/absolute expiry, logout, staff disablement, password/email/security changes, legacy-host compatibility, and cross-host handoff or explicit reauthentication without account duplication.
  7. Migration: run dry-run twice, apply twice, and reconcile account IDs, aliases, every dependent table, totals/checksums, ambiguity report, disabled duplicate behavior, and no outbound side effects. Run forward/backward migration-window tests against a production-like database copy.
  8. Rollback: create account-owned writes after cutover, roll the application back without reversing data, and prove both old/new identifiers resolve safely, sessions follow policy, new writes remain, and only one account authenticates.
  9. API parity: exercise representative session-authenticated and compatibility token/principal requests before/after migration; assert same account, schema/status/authorization behavior, safe 401/403, and no cross-account lookup leak.
  10. Anonymized review: load P0: Build a fully navigable Django review skeleton #97 content fixtures and assert zero new user, email/social, session, enrollment, registration, token, and staff rows plus a PII/secret artifact scan.

Browser scenarios

Run at desktop and mobile widths with deterministic synthetic accounts:

  1. Start signed out on /, use the shared Login control, authenticate as an existing learner, return via next, and navigate articles/events/courses/account settings without another login or account ID change.
  2. From the same signed-in shell, traverse course dashboard/homework/project/profile surfaces; verify account navigation, profile/social-link values, theme/timezone preferences, logout, browser-back privacy, and no legacy/second login dead end.
  3. Exercise a deterministic social-provider callback for an existing account and a duplicate/conflict denial. The successful path lands on the same account; the conflict gives safe recovery guidance without exposing whether another account exists.
  4. Use a staff account through the shared shell; authorized Studio navigation is present, learner navigation still uses the same account, and a learner/disabled staff receives the expected denial without a second staff identity.
  5. Exercise the approved legacy-host-to-canonical continuity or explicit-reauth flow, including intended destination, back/refresh/replay/logout, and verify no token appears in URL/history/screenshot.
  6. Load the P0: Build a fully navigable Django review skeleton #97 content-only review dataset signed out and signed in; shared navigation changes appropriately while database counts prove the content import itself created no identity or PII.

Capture and inspect representative desktop/mobile login, account-menu/settings, returning learner, staff allowed/denied, duplicate-conflict, and continuity/reauth states under .tmp/, with synthetic identifiers only.

Dependencies and coordination

Delivery convention

Follow _docs/PROCESS.md. Engineering implements and tests without committing; an independent tester verifies migration/browser evidence and synthetic screenshots; product acceptance follows. No pull request is created.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must-have or release-blockingauthArea: authcoursesArea: coursesdata-migrationArea: data-migrationenhancementNew feature or requestfrontendArea: frontendhumanCode complete; manual verification requiredintegrationArea: integrationsecurityArea: securitytestingArea: testing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions