Skip to content

Authentication & User Management #6

@aniebietafia

Description

@aniebietafia

Feature: Implement POST /api/v1/auth/signup — User Registration Endpoint

Problem
FluentMeet has no way for new users to register. There is no signup endpoint, no user creation logic, no password hashing, and no email verification flow. Without this, the platform cannot onboard any users and every subsequent authenticated feature (meetings, profiles, settings) remains inaccessible.

Proposed Solution
Implement the POST /api/v1/auth/signup endpoint that accepts user credentials and language preferences, validates the input, hashes the password, persists the user to the database, and triggers an account verification email via Kafka. The new user account starts in an unverified state (is_verified=False) and cannot access protected endpoints until the verification link is clicked.

User Stories

  • As a new user, I want to register with my email, password, and language preferences in a single request, so I can get started with FluentMeet quickly.
  • As a new user, I want to receive a verification email immediately after signing up, so I know my registration was successful and can activate my account.
  • As a developer, I want the signup endpoint to reject duplicate email addresses with a clear error, so users are not confused when they try to register with an email that already exists.
  • As a security engineer, I want passwords to be hashed with bcrypt before storage and never logged or returned in any response, so user credentials are protected even in the event of a database breach.

Acceptance Criteria

  1. POST /api/v1/auth/signup accepts the following JSON body:
    {
      "email": "user@example.com",
      "password": "MyStr0ngP@ss!",
      "full_name": "Ada Lovelace",
      "speaking_language": "en",
      "listening_language": "fr"
    }
  2. Input validation (enforced by Pydantic):
    • email — valid email format; required.
    • password — minimum 8 characters; required.
    • speaking_language and listening_language — valid BCP-47 language codes (e.g., "en", "fr", "de"); default to "en" if omitted.
    • full_name — optional string, max 255 characters; stripped of leading/trailing whitespace.
  3. If email already exists in the database, the endpoint returns 409 Conflict using the standard error schema:
    { "status": "error", "code": "EMAIL_ALREADY_REGISTERED", "message": "An account with this email already exists.", "details": [] }
  4. The user's password is hashed with bcrypt (via passlib) before being stored. The plaintext password is never persisted or logged.
  5. A new User record is created in PostgreSQL with is_active=True, is_verified=False, and deleted_at=None.
  6. On successful creation, a message is published to the notifications.email Kafka topic to trigger a verification email (see Email Service issue):
    {
      "to": "user@example.com",
      "subject": "Verify your FluentMeet account",
      "template": "verification",
      "data": { "full_name": "Ada Lovelace", "verification_link": "https://..." }
    }
  7. The endpoint returns 201 Created with the new user's public profile:
    {
      "id": 1,
      "email": "user@example.com",
      "full_name": "Ada Lovelace",
      "speaking_language": "en",
      "listening_language": "fr",
      "is_active": true,
      "is_verified": false,
      "created_at": "2026-03-14T12:00:00Z"
    }
  8. The password, hashed_password, and any internal fields are never included in the response.
  9. The endpoint is rate-limited to 10 requests/minute per IP (see Security & Rate Limiting issue).
  10. Unit and integration tests cover: successful signup, duplicate email conflict, validation errors, and Kafka message publication.

Proposed Technical Details

  • Router: app/api/v1/endpoints/auth.py — new POST /signup route.
  • Schema: UserCreate (already in app/schemas/user.py) — email, password (min 8 chars), full_name, speaking_language, listening_language.
  • CRUD: app/crud/user.pycreate_user(db, user_in) function; checks for existing email, hashes password via passlib.context.CryptContext(schemes=["bcrypt"]), inserts record.
  • Email Trigger: EmailProducerService.send_email(...) called after successful DB commit; failure to publish to Kafka should not roll back the user creation — it should be logged and retried separately.
  • Verification Token: A VerificationToken record is created alongside the user and its value embedded in the verification link (/api/v1/auth/verify-email?token=<uuid>). Token expires after 24 hours.
  • New/Modified Files:
    • app/api/v1/endpoints/auth.py [NEW]
    • app/crud/user.py [NEW]
    • app/models/verification_token.py [NEW]
    • app/schemas/auth.pySignupResponse [NEW]
    • app/api/v1/api.py — register auth router [MODIFY]
    • Alembic migration for verification_tokens table [NEW]

Tasks

  • Create app/crud/user.py with get_user_by_email and create_user functions.
  • Implement VerificationToken SQLAlchemy model in app/models/verification_token.py.
  • Generate and apply Alembic migration for verification_tokens table.
  • Implement POST /api/v1/auth/signup in app/api/v1/endpoints/auth.py.
  • Add ConflictException with code EMAIL_ALREADY_REGISTERED to the signup CRUD logic.
  • Integrate EmailProducerService to publish verification email after user creation.
  • Apply @limiter.limit("10/minute") rate limit decorator to the signup route.
  • Register the auth router in app/api/v1/api.py.
  • Write unit tests for create_user CRUD (mock DB session).
  • Write integration tests for the full signup flow (duplicate email, validation errors, successful 201).

Open Questions/Considerations

  • Should signup be open to everyone (public), or should we support invite-only registration (requiring a valid invite token)?
  • What should happen if the Kafka email publish fails at signup time — silent retry, or notify the user that the verification email will be delayed?
  • Should the verification token be a UUID or a signed JWT (which avoids a database lookup on verification)?
  • Should we enforce a minimum password strength policy beyond the 8-character minimum (e.g., require numbers and symbols)?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions