Identity Management =================== Plant Tracer uses several identities with different ownership and security properties. Do not overload the word ``admin`` for all of them. Server Sender Identity ---------------------- ``SERVER_EMAIL`` is the application sender address used for outgoing Plant Tracer email. In the Lambda-only stack this is ``admin@planttracer.com``. This address is not a course administrator and is not a login account for a teacher. It is the verified SES identity that the stack is allowed to send from. ``SERVER_EMAIL_NAME`` is the display name paired with ``SERVER_EMAIL`` in the MIME ``From`` header. The default stack value is ``Plant Tracer``. The Lambda-only stack defines both values and passes them to ``lambda-web`` as environment variables. The mailer uses: * ``SERVER_EMAIL`` as the bare SMTP/SES envelope sender and SES ``Source``. * ``SERVER_EMAIL_NAME `` as the human-readable MIME ``From`` header. Keeping those values separate matters because SES IAM permissions and ``ses:FromAddress`` conditions are scoped to the sender email address, while users should see a readable product name in their mail client. User Identity ------------- Plant Tracer users are identified by email address. The ``users`` table stores the durable user profile: * ``user_id``: the stable internal identifier. It is generated by the application and should be used for joins, ownership checks, and permissions. * ``email``: the user's login and contact address. It is also stored in the ``unique_emails`` table so that registration can enforce one user profile per email address. * ``user_name``: the user's display name. This is the name shown in page headers, movie lists, course user lists, and some movie metadata defaults. * ``enabled``: whether the user can authenticate. * ``primary_course_id`` and ``primary_course_name``: the course context used by normal page rendering and uploads. * ``courses``: the course ids in which the user is enrolled. * ``admin_for_courses``: the course ids for which the user has course administrator privileges. The email address is the human-facing identity, but ``user_id`` is the durable authorization and ownership key. Do not store relationships by user name, and do not assume that a display name is unique. Names ----- ``user_name`` is editable display metadata, not an authentication credential. The browser presents this as "Name" on pages such as ``/list``, ``/upload``, and ``/audit``. Those pages use a pencil editor to send updates through ``POST /api/set-metadata`` for the active user. Changing ``user_name`` changes future display of the user profile, but it does not change existing denormalized movie owner names that may already be stored on movie records. Code that needs the current user name should read the user profile; code that needs historical movie display should tolerate the movie row having its own stored ``user_name`` value. The same pages currently expose the user's email address for editing. Email is security-sensitive because it is the login identity and uniqueness key. Backend email changes must update both ``users.email`` and ``unique_emails.email`` transactionally; otherwise two user profiles could claim the same address or a login link could be sent to an address that no longer maps cleanly to the user. Course Administrators --------------------- ``ADMIN_EMAIL`` and ``ADMIN_NAME`` refer to a course administrator, typically the instructor or teacher for a course. They are data-plane identities stored in DynamoDB user/course records. They are not stack sender identities. A course administrator can: * manage the course they administer; * register students for that course; * receive course setup/login email through the configured Plant Tracer sender. Course administrators should be created or updated through ``dbutil`` and operator Makefile targets. They should not be configured as SAM stack parameters because courses and users live in DynamoDB, and DynamoDB data now outlives individual lambda-only stacks. Operators can list current course administrators with ``make admin-list``. The target runs ``src/dbutil.py admin-list`` against the selected AWS/DynamoDB environment and prints each administrator's display name, email address, ``user_id``, and administered courses. Set ``AWS_REGION`` and ``DYNAMODB_TABLE_PREFIX`` for the target environment before running it; local development defaults still use DynamoDB Local and the ``demo-`` prefix. The operator-facing administrator list is built by ``app.course_management.list_admins`` so future web administration pages can reuse the same read model. Operators can create or update course administrators with ``make admin-create``. The target runs ``src/dbutil.py admin-create``. In an interactive terminal it asks for administrator email/name, lists courses where that user is not already an administrator, and accepts one or more selected courses. Non-interactive automation can pass ``ADMIN_CREATE_FLAGS``, for example ``ADMIN_CREATE_FLAGS="--admin_email teacher@example.edu --admin_name 'Teacher Name' --course_id BIO101 --planttracer_endpoint https://example.planttracer.com"``. By default the command sends the administrator a course setup/login email through the configured mail path. Use ``--planttracer_endpoint`` or set ``HOSTNAME``/``DOMAIN`` so generated links point at the intended stack. Use ``--no-send-email`` for data-only dry runs, or ``MAILER_DRY_RUN=true`` to render mail without sending it. Operators can create courses with ``make course-create``. The target runs ``src/dbutil.py create-course --send-email``. In an interactive terminal it asks for course id/number, course name, and course administrator. Existing course administrators are listed first so the operator can select one; pressing Enter creates a new administrator from the prompted email/name. Non-interactive automation can pass ``COURSE_CREATE_FLAGS``, for example ``COURSE_CREATE_FLAGS="--course_id BIO101 --course_name 'Plant Biology 101' --admin_email teacher@example.edu --admin_name 'Teacher Name' --planttracer_endpoint https://example.planttracer.com"``. The reusable course/admin creation logic is in ``app.course_management`` so that future web administration pages can call the same operation without shelling out to ``dbutil``. If the course already exists with the same name, the command treats that as an operator retry and ensures the selected administrator relationship and email step. If the existing course name differs, the command fails rather than silently reusing the wrong course id. For a deployed Lambda-only stack, prefer ``make sam-course-create``. It reads ``stack_name`` from the selected ignored ``SAM_CONFIG`` file, resolves the stack's ``DynamoDBTablePrefix``, ``ApplicationUrl``, and ``MailerDryRun`` settings from CloudFormation, and then delegates to ``src/dbutil.py create-course --send-email``. Pass the same ``COURSE_CREATE_FLAGS`` used by ``make course-create``. This keeps course initialization separate from stack deployment while reducing the chance of creating course data in the wrong table prefix or sending links for the wrong host. The demo course has its own narrower target: ``make demo-course-create``. That target runs ``src/dbutil.py create-demo-course`` and ensures only the durable demo course data exists: ``demo-course``, the demo course administrator, the demo user, and the fixed demo-mode API key. It does not create tables, upload objects, or seed demo movies. Run it after deploying a demo stack, or any time the selected DynamoDB prefix needs the demo course repaired. The command uses the current AWS/DynamoDB environment, so set ``AWS_REGION`` and ``DYNAMODB_TABLE_PREFIX`` for the target database before running it. For local development with local services and seeded demo movies, continue to use ``make make-local-demo``. That target runs table creation, demo course creation, and sample movie seeding as separate ``dbutil`` commands; there is no combined ``dbutil create-demo`` command in the lambda-only workflow. Courses ------- Courses are durable application data. ``COURSE_ID`` and ``COURSE_NAME`` should not be stack parameters or Lambda runtime environment variables. Course creation should be an explicit, idempotent post-deploy data initialization step against the selected ``DYNAMODB_TABLE_PREFIX``. Course Keys ----------- ``course_key`` is a course registration secret, not a user password and not an API key. It is stored on the ``courses`` table and indexed so that the registration page can find the course from the key. The public registration flow is: 1. A user enters email address, display name, and course key on ``/register``. 2. ``POST /api/register`` validates the email and course key. 3. ``register_email`` creates or updates the user, enrolls the user in the course, and records the ``course_users`` join row. 4. The application creates a new ``api_key`` for that user. 5. The mailer sends a magic login link to the user's email address. Course keys only authorize enrollment in a course. After enrollment, normal page and API authorization is based on ``api_key`` and course membership. API Keys And Login Links ------------------------ Plant Tracer authenticates requests with ``api_key`` values. An API key is a bearer token stored in the ``api_keys`` table and linked to one ``user_id``. The table records whether the key is enabled, when it was created, how many times it has been used, and first/last use timestamps. Magic links are email messages containing a URL with an ``api_key`` query parameter, for example ``/login?api_key=...`` or ``/list?api_key=...``. When a browser opens one of these links, the server reads the API key from the query string and writes it into the ``api_key`` cookie. Later browser requests normally authenticate from the cookie. JavaScript pages also receive the active key as the ``api_key`` browser global for API calls that still submit the key explicitly. The ``/resend`` flow does not create a new user. It looks up an existing user by email, creates a fresh API key for that user, and sends a new magic link. API keys are bearer credentials. Anyone who can read a magic link or cookie can act as that user until the key is disabled or expires by policy. Logs and operator tools should avoid exposing API keys. This is why many API calls use ``POST`` and why dry-run mail must only be used with test users and test data. Outgoing login and course setup email should always be sent from ``SERVER_EMAIL`` / ``SERVER_EMAIL_NAME``. The recipient may be a course administrator, a student, or another registered user, but the sender identity remains the server sender. Authorization Model ------------------- The authenticated user comes from ``validate_api_key(api_key)``. A valid key returns the linked user only if both the key and the user are enabled. Normal ownership and permission checks use: * ``user_id`` for movie ownership and profile updates. * ``courses`` and ``course_users`` for course membership. * ``admin_for_courses`` and ``admins_for_course`` for course administrator privileges. * ``primary_course_id`` for the current course context on rendered pages and uploads. Course administrators are still normal users. Their extra authority applies to the courses listed in their administrator fields; it does not make their email address a stack sender identity and does not make them a global application administrator. Dry-Run Mail ------------ Non-production stacks whose operators cannot send SES mail as ``SERVER_EMAIL`` can set ``MailerDryRun=true``. Dry-run mode renders mail and writes it to logs instead of sending it. Use this only with test users and test data because the rendered email includes login links and API keys.