diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..b521032 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,80 @@ +# Overview of the API endpoint routing + +The common prefix for all API routes is `/api`. All resource IDs are of type UUIDv4. Any routes utilizing pagination have the defaults of 50 for limit and 0 for offset. + +## Auth + +Unprotected endpoints: + +- `POST /auth/signup`: Create a new user with `username` and `password` -> Created user's username and ID +- `POST /auth/login`: Login to an existing user with `username` and `password` -> Cookie with refresh token and response with access token (including user data if `includeUser` URL parameter is `true`) + +Endpoints protected with `requireAccessToken` middleware: + +- `GET /auth/me`: Get own user's data -> `userResponse` DTO (user ID, username, admin status, and timestamps of creation and last update) +- `POST /auth/logout`: Logout the current user -> Cookie that replaces the refresh token with one that expires immediately +- `PUT /auth/owner/`: Update password of the current user with `old_password` and `new_password` -> HTTP 204 response +- `DELETE /auth/owner/`: Delete the current user (as the owner) with `password` -> HTTP 204 response +- `GET /auth/admin/all`: As an administrator, list all users stored in the system (adjustable with pagination URL parameters) -> Array of `userResponse` DTOs +- `DELETE /auth/admin/{userID}`: As an administrator, delete a specific user -> HTTP 204 response + +Endpoints protected with `requireRefreshToken` middleware: + +- `POST /auth/refresh`: Perform token rotation (revokes the old refresh token server-side) -> Cookie with new refresh token and response with access token + +## Notes + +All notes related endpoints are protected with `requireAccessToken` middleware, which means that `Bearer ` must be included into the Authorization header of each request. The middleware automatically attaches JWT's user claims into the request's context. + +- `POST /notes`: Create a new note -> Placeholder contents (`title` and `content`) +- `GET /notes`: List the metadata of all owned notes -> Array of `data.ListNoteRows` (note ID, owner ID, title, and timestamp of last update) +- `GET /notes/{noteID}`: Get the full representation of a specific note -> `data.GetFullNoteRow` (metadata + current version's title and content) +- `DELETE /notes/{noteID}`: Delete a specific note -> HTTP 204 response +- `GET /notes/{noteID}/versions`: Get version metadata history of a specific note (adjustable with pagination URL parameters) -> Array of `data.GetVersionHistoryRow` (version ID, title, version number, and timestamp of creation) +- `POST /notes/{noteID}/versions`: Create a new version for a specific note with `title` and `content` -> HTTP 204 response +- `GET /notes/{noteID}/{versionID}`: Get the full representation of a specific version of a specific note -> `data.GetVersionRow` (metadata + the version's content) + +# Practical client usage + +## Auth sequence + +![Sequence diagram of the authentication flow](./media/auth-sequence.svg) + +- Store access token in memory (never in `localStorage`) +- Automatically handle 401 responses by attempting token refresh +- Queue pending requests during token refresh, if necessary +- Clear local tokens on logout (`POST /auth/logout`) + +## Flow of accessing protected resources + +![Flowchart of accessing a protected resource](./media/protected-resource-flow.svg) + +## Flow of accessing versioned notes + +![Flowchart of perfoming actions for versioned note](./media/versioned-note-flow.svg) + +## Admin resources + +- Verification of the presence of `isAdmin` tag before rendering +- Graceful handling of 403 errors +- Automatic refresh of locally cached users list (if any) after admin actions (e.g. deleting users) + +## Error cases + +- Invalid token (401): + - While using access token -> Automatically attempt refresh + - While using refresh token -> Redirect to login page +- Insufficient permissions (403): Don't render the corresponding route +- Resource not found (404): Render a dedicated 404 view + +## State management + +- Authentication state +- Resource state (current note) +- Pagination state (current page, page size, and total locally cached items) + +## Necessary client interceptors + +- Requests: Add Authorization header with either `access_token` or `request_token` +- Responses: Handle 401 -> Attempt to automatically rotate the tokens +- Errors: Handle network errors + [aforementioned](#error-cases) API error formats diff --git a/docs/media/auth-sequence.svg b/docs/media/auth-sequence.svg new file mode 100644 index 0000000..1107559 --- /dev/null +++ b/docs/media/auth-sequence.svg @@ -0,0 +1,4 @@ + + + +
Client
API
POST /api/auth/login
Set refresh_token httpOnly cookie & return access_token
[ Protected request ] (401 if access token is expired)
POST /api/auth/refresh (refresh token as bearer)
Set new refresh_token httpOnly cookie & return access_token
Retry [ Protected request ]
Check if refresh_token 
is in a cookie, otherwise 
redirect to login page
Check if given refresh_token 
can be found in the database
\ No newline at end of file diff --git a/docs/media/protected-resource-flow.svg b/docs/media/protected-resource-flow.svg new file mode 100644 index 0000000..6ae0cd6 --- /dev/null +++ b/docs/media/protected-resource-flow.svg @@ -0,0 +1,4 @@ + + + +
Access a protected resource
Is locally stored 
access token 
available?
No
Make API request with access token as bearer
Yes
Does response 
contain 401 
status scode?
Request was 
successful
No
Yes
Attempt token refresh
 and attempt to access again
Is still 
unauthorized?
Yes
No
Redirect to 
login page
\ No newline at end of file diff --git a/docs/media/versioned-note-flow.svg b/docs/media/versioned-note-flow.svg new file mode 100644 index 0000000..b57f11c --- /dev/null +++ b/docs/media/versioned-note-flow.svg @@ -0,0 +1,4 @@ + + + +
DOM 
initialization
Load metadata list:
GET /api/notes
Note selection
Load full note contents:
GET /api/notes/{noteID}
Pick next action
View history
Create new 
version
Delete
Load version history:
GET ../{noteID}/versions
Select version
Load full version contents:
GET ../versions/{versionID}
Delete the note:
DELETE ../{noteID}
Create new version:
POST ../{noteID}/versions
Action done (end)
\ No newline at end of file