Files
notesvault/README.md
2026-06-02 09:12:04 -06:00

8.5 KiB

Secure Notes Vault API

System overview

Secure Notes Vault API is a Spring Boot REST service for creating, managing, and sharing private notes. Users register and log in with username/password credentials, then authenticate subsequent requests with a JWT bearer token.

The application is organized around a standard controller-service-repository flow:

  • Controllers expose the authentication and notes REST endpoints.
  • Spring Security and a JWT filter authenticate protected requests.
  • Services enforce note ownership and read-only shared-note access rules.
  • JPA repositories persist users, notes, and note shares.
  • Flyway migrations manage the database schema for both local H2 and PostgreSQL-backed runs.

Running the program

This project requires Java 21 and Maven. The API starts on http://localhost:8080.

For the fastest local setup, run the app with the in-memory H2 database:

make dev

To run the test suite:

make test

To run the API and PostgreSQL with Docker Compose:

make docker-up

Stop the Docker Compose stack with:

make docker-down

You can also run the Spring Boot app directly:

JWT_SECRET=dev-secret-change-me-do-not-use-in-production mvn spring-boot:run

Design decisions

  • Java programming language since Bluestaq is a Java shop
  • Maven for building
  • Spring Boot as a framework
  • H2 for persistence - plan for Postgres
  • JPA/Hibernate for SQL
  • Flyway for DB schema control
  • JWT for auth

Test App

A React app located at https://git.seanstarkey.dev/starkey/notes was used to test the API. This app exercises the API and displays REST requests/responses for debugging. It runs in a docker container.

API Endpoints

Authentication

Method Endpoint Description
POST /auth/register Register a new user
POST /auth/login Authenticate and receive a JWT

Notes

Method Endpoint Description
POST /notes Create a new note
GET /notes List owned notes and notes shared with you
GET /notes/{id} Get a note by ID (owner or shared recipient)
PUT /notes/{id} Update a note (owner only)
DELETE /notes/{id} Delete a note (owner only)
POST /notes/{id}/share Share a note with another user (read-only)

API Usage Examples

The following examples assume the API is running at http://localhost:8080.

Register two users:

curl -i -X POST http://localhost:8080/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"password123"}'

curl -i -X POST http://localhost:8080/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"bob","password":"password123"}'

Log in and save each JWT:

ALICE_TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"password123"}' | jq -r '.token')

BOB_TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"bob","password":"password123"}' | jq -r '.token')

Create a note as Alice:

NOTE_ID=$(curl -s -X POST http://localhost:8080/notes \
  -H "Authorization: Bearer $ALICE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Launch checklist","content":"Rotate credentials before launch."}' | jq -r '.id')

List Alice's visible notes:

curl -i http://localhost:8080/notes \
  -H "Authorization: Bearer $ALICE_TOKEN"

Share Alice's note with Bob:

curl -i -X POST http://localhost:8080/notes/$NOTE_ID/share \
  -H "Authorization: Bearer $ALICE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"username":"bob"}'

Bob can read the shared note:

curl -i http://localhost:8080/notes/$NOTE_ID \
  -H "Authorization: Bearer $BOB_TOKEN"

Bob cannot update the shared note because shared access is read-only:

curl -i -X PUT http://localhost:8080/notes/$NOTE_ID \
  -H "Authorization: Bearer $BOB_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Edited by Bob","content":"This update should be rejected."}'

HTTP return codes

Application-defined HTTP status codes are generated in AuthController for successful registration and in GlobalExceptionHandler for API error responses. Spring Security and Spring MVC may still produce framework-level responses for requests that do not reach a controller, such as unauthenticated protected requests or unsupported methods.

HTTP code Status Generated in Trigger Response body
201 Created AuthController.register POST /auth/register creates a new user. UserResponse with id, username, and createdAt.
400 Bad Request GlobalExceptionHandler.handleMalformedJson Request body cannot be parsed or converted from JSON. ErrorResponse with Malformed request body.
400 Bad Request GlobalExceptionHandler.handleTypeMismatch Path variable or query parameter cannot be converted to the expected type. ErrorResponse with Invalid request parameter.
401 Unauthorized GlobalExceptionHandler.handleAuthentication; Spring Security filter chain Authentication fails or an unauthenticated request targets a protected route. ErrorResponse with Invalid username or password when handled by GlobalExceptionHandler; framework-generated body when rejected before controller handling.
403 Forbidden GlobalExceptionHandler.handleForbidden Authenticated caller is not allowed to perform the requested operation. ErrorResponse with the exception message.
404 Not Found GlobalExceptionHandler.handleNotFound Requested domain resource does not exist or should be hidden from the caller. ErrorResponse with the exception message.
404 Not Found GlobalExceptionHandler.handleNoResourceFound No controller route or static resource matches the request. ErrorResponse with Not found.
409 Conflict GlobalExceptionHandler.handleConflict; AuthController.register throws ConflictException Duplicate username or other persistence/domain uniqueness conflict. ErrorResponse with the exception message.
422 Unprocessable Entity GlobalExceptionHandler.handleValidation Bean Validation fails for a request body annotated with @Valid. ValidationErrorResponse with error and per-field fields.

Database

  • The schema is the basic requirements for a User, Note and NoteShare.
  • H2 database is used for development.
  • Flyway will allow us to migrate to Postgres for "production".
Table Column Type Required Key / Constraint Description
users id UUID Yes Primary key Unique user identifier.
users username VARCHAR(100) Yes Unique: uq_users_username User login handle.
users password_hash VARCHAR(255) Yes BCrypt password hash.
users created_at TIMESTAMP WITH TIME ZONE Yes Account creation timestamp.
notes id UUID Yes Primary key Unique note identifier.
notes owner_id UUID Yes FK to users(id); indexed by idx_notes_owner_id User who owns the note.
notes title VARCHAR(255) Yes Note title.
notes content TEXT Yes Note body content.
notes created_at TIMESTAMP WITH TIME ZONE Yes Note creation timestamp.
notes updated_at TIMESTAMP WITH TIME ZONE Yes Last note update timestamp.
note_shares id UUID Yes Primary key Unique share identifier.
note_shares note_id UUID Yes FK to notes(id) with ON DELETE CASCADE; indexed by idx_note_shares_note_id Note being shared.
note_shares shared_with_user_id UUID Yes FK to users(id); indexed by idx_note_shares_shared_with_user_id User receiving read-only access.
note_shares created_at TIMESTAMP WITH TIME ZONE Yes Share creation timestamp.
note_shares note_id, shared_with_user_id UUID, UUID Yes Unique: uq_note_shares_note_user Prevents duplicate shares for the same note and user.

References

A list of projects looked at for reference.