This document outlines the project configuration and roadmap for the Rust-based GraphQL Auth Service for Dimensional Pocket. The service will provide REST endpoints, a GraphQL API, and user management functionalities.
- Project Name:
dp-auth-service
- Indentation: Use 2 spaces for Rust code (not the standard 4 spaces)
- GraphQL Library:
async-graphql
- Database:
sqlx
with SQLite on a file in the repo root'sdata
directory (will be mounted in production) - REST endpoints:
/
and/health
, viaaxum
- GraphQL Endpoint:
/graphql
- Async Runtime:
tokio
- Password Hashing:
argon2
; 16-byte salt generated withrand::rngs::OsRng
- Design Patterns:
- GraphQL queries and mutations only call Service objects and handle the response
- E.g.,
getServerTimestamp
query callsServerService::get_server_timestamp
- Service objects are static and do not require instantiation
- Service objects have their own unit tests with full coverage
- GraphQL Query and Mutation tests only test if they're calling the Service methods with the correct parameters
- Service objects are stored in
src/services
- Each GraphQL query and mutation are implemented in independent files in
src/graphql/queries
andsrc/graphql/mutations
respectively- Files are named directly after the query or mutation they implement, e.g.,
get_server_timestamp.rs
for thegetServerTimestamp
query - The structs for the queries and mutations are suffixed with
Query
orMutation
- E.g.,
GetServerTimestampQuery
for thegetServerTimestamp
query
- E.g.,
- Files are named directly after the query or mutation they implement, e.g.,
- E.g.,
- SQL Queries are executed via SQL Query objects
- E.g.,
UserByIdQuery::run(user_id)
- All SQL query objects have a
run
method (arguments may vary) that executes the database query and returns the result - SQL Query objects are static and do not require instantiation
- SQL Query objects are stored in
src/queries
- E.g.,
- GraphQL queries and mutations only call Service objects and handle the response
- Initialize Rust project
- Configure
async-graphql
,axum
,sqlx
, andtokio
- Create REST endpoints:
-
/
- Returns a simple "OK" message -
/health
- Returns a simple "OK" message
-
- Implement
getServerTimestamp
GraphQL query and associated Service object:- Returns the current server timestamp (milliseconds since epoch)
- Test the REST endpoints,
getServerTimestamp
query, and the Service object - Document the API endpoints and query
- Suggest industry standards for logging endpoints and queries in Rust/GraphQL
- Implement logging for existing REST endpoints and GraphQL queries (check Phase 1 for a list of endpoints and queries)
- For REST endpoints, log the request method, path, response status, and response time
- For GraphQL queries, log the operation name and response time (no parameters)
- Implement
PasswordService
for user password management-
generate
method to create a new password hash- Already includes the salt generation
-
verify
method to check a password against a hash
-
- Test the
PasswordService
methods - Document the
PasswordService
methods via Rust doc comments
- Configure
sqlx
to use SQLite database indata/development.db
(filename to come from environment variable) - Create database schema with
sqlx
migrations- Create migration for
user_roles
table - Create migration for
users
table - Schema defined with proper foreign key constraints
- Migrations stored in
config/database/migrations
- Create migration for
- Run migrations to create the database schema
- Verify support for schema dump after running migrations, to be stored in
config/database/schema.sql
- Implement SQL Query objects following project patterns
- Create comprehensive unit tests for all query objects
- Create integration tests demonstrating complete user creation flow
- Implement seeds system with default user roles
- Suggest industry standards for user roles and permissions in Rust/GraphQL
- Permissions are granular (e.g.,
can_create_user
,can_delete_user
, etc.) and can be assigned to roles - Update the database schema to store permissions for roles (details to be defined)
- Implement
UserRoleService
for managing user roles and checking permissions-
get_role_by_id
method to retrieve a role by ID -
get_role_by_name
method to retrieve a role by name -
check_permission
method to check if a user has a specific permission
-
- Implement
UserService
for user management-
create_user
method to create a new user- Validates input (username and password) and checks for username already in use
- Uses
PasswordService
to hash the password - Assigns default role to the user
-
get_user_by_id
method to retrieve a user by ID -
get_user_by_name
method to retrieve a user by name
-
- Implement
createUser
GraphQL mutation- Calls
UserService::create_user
- Returns the created user object
- Calls
- Unit tests for the
createUser
mutation- Tests should verify that the mutation calls the
UserService::create_user
method with the correct parameters
- Tests should verify that the mutation calls the
- Integration tests for the
createUser
mutation- Call the endpoint to create a user
- Implement
SessionService
for managing session tokens- We're using a custom encrypted token format, not JWT
- Why not JWT? Because we don't want to expose the payload structure to consumers
- Consumers will call a future
getCurrentSession
query to retrieve the session payload with the token in the request (header or cookie)
- Define the payload structure for the session token - JSON-serializable
- Fields:
sub
(subject - user id),iat
(issued at timestamp),exp
(expiration timestamp)
- Fields:
- Requires a secret key for signing tokens, stored in an environment variable (
DP_AUTH_SECRET_KEY
)- Determine how the service will act if the secret key is not set
-
encode_token
method to create a session token from a payload -
decode_token
method to decode a session token and retrieve the payload
- We're using a custom encrypted token format, not JWT
- Test the
SessionService
methods - Document the
SessionService
methods via Rust doc comments
- Implement middleware for session token management, to be used in the GraphQL API only (all queries and mutations)
- Middleware should:
- Check for the session token in the request header or cookie, in that order (if both present, prefer the header)
- Do not fallback to cookie if the header token is present but invalid
- Decode the token using
SessionService::decode_token
- If valid, attach the decoded session token payload to the request context
- If invalid or missing, don't attach the payload and allow the request to proceed without it (each resolver will handle the absence of the payload)
- Check for the session token in the request header or cookie, in that order (if both present, prefer the header)
- Decisions to make:
- What is the prefix for the auth header, considering it's a custom encrypted token? ("Bearer" or something else?)
- Do we need to know the cookie name? If so, use
DpAuthSession
as the cookie name and store this string in a constant
- Allow resolvers to have access to the session token payload via the request context
- Resolvers can then propagate the session token payload to the Service objects on a case-by-case basis
- Unit tests to ensure the middleware is attaching the session token payload when valid
- Integration tests will be implemented in a later phase, by resolvers that actually use the session token payload
- Accepts username and password as input
- Calls
UserService::get_user_by_name
to retrieve the user by username - Calls
PasswordService::verify
to check the password against the stored hash - If valid, calls
SessionService::encode_token
to create a session token - Returns the session token, or an error if the credentials are invalid
- Error message should be specific (e.g., "User is blank" or "User not found" or "Password is blank" or "Password does not match", etc)
- Errors should be logged using the existing logging system and must contain the given username (not the password) for debugging
- Errors should be logged at
info
level, notwarn
orerror
-- those errors should not raise alarms in our logs, they're just infomational
- Verify existing CORS configuration for 'with_credentials' support
- Unit tests for the
create_session
method - Document the
create_session
method via Rust doc comments
- Implement
createSession
GraphQL mutation- Accepts username and password as input
- Calls
SessionService::create_session
- On success, returns the session token and sets the cookie with the token in the response
- On failure, returns a user-friendly error message (not the internal error message), e.g., "Invalid credentials" for any username or password error, or "Internal server error" for unexpected errors
- Make a decision if errors should return 2XX or 4XX status codes, as it impacts the client-side error handling (decision: 2XX)
- Unit tests for the
createSession
mutation, ensuring it calls theSessionService::create_session
method with the correct parameters - Integration tests for the
createSession
mutation, ensuring it returns a session token and sets the cookie in the response
- Returns the current session token payload, set by the session middleware
- Make a decision if the query should return 2XX or 401 status code if the session token is not present or invalid
- Unit tests for the
getCurrentSession
query - Integration tests for the
getCurrentSession
query
- Implement a 404 handler for the REST API
- Requests to non-existent endpoints should return a 404 status code with a plain "NOT FOUND" message
- Requests should be logged with the request method, path, querystring, and body size (if present); log level should be
info
- Integration tests for the 404 handler
- Email support
- Cookie-less session management (using custom headers in response)
- Password change (when logged in)
- Password reset (requires email support)
- Username change
- User deletion/redaction
user_sessions
table to track user sessions and make tokens revocable