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_EMAILas the bare SMTP/SES envelope sender and SESSource.SERVER_EMAIL_NAME <SERVER_EMAIL>as the human-readable MIMEFromheader.
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 theunique_emailstable 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_idandprimary_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:
A user enters email address, display name, and course key on
/register.POST /api/registervalidates the email and course key.register_emailcreates or updates the user, enrolls the user in the course, and records thecourse_usersjoin row.The application creates a new
api_keyfor that user.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.
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.