A social knowledge tool for researchers built on ATProto
User Domain Model (DDD)#
This document outlines the domain model for user management within this application, specifically focusing on linking existing Bluesky accounts via the ATProto OAuth flow.
Bounded Contexts#
We introduce a new User Management Context, distinct from the Annotations context.
- User Management Context: Responsible for representing users within this application, linking them to their external Bluesky identity via OAuth, and managing their session state.
- Annotations Context: (Existing) Manages annotations, fields, and templates. Users from the User Management context will be the actors performing actions within the Annotations context.
- ATProto Context: (Existing) Provides low-level ATProto types (
StrongRef,TID) and potentially infrastructure adapters for interacting with the AT Protocol (including OAuth).
Layered Architecture (User Management Context)#
1. Domain Layer#
Contains the core representation of a user within this application.
-
Aggregates & Entities:
User(Aggregate Root):- Represents a user account within this application.
id:string(The user's Bluesky DID -did:plc:...). This acts as the unique identifier linking to the external identity and the primary key within this context.handle:string(The user's Bluesky handle at the time of linking or last refresh). Optional or fetched on demand.linkedAt:Date(Timestamp when the account was first linked via OAuth).lastLoginAt:Date(Timestamp of the last successful OAuth authentication/refresh).- (Potentially other application-specific user profile data)
- Note: This aggregate does not directly store OAuth tokens (access/refresh). Token management is handled separately as part of the authentication session state, likely managed by the infrastructure layer based on the OAuth client library's needs.
-
Value Objects:
DID: Represents a validateddid:plcstring.Handle: Represents a validated Bluesky handle string.
-
Domain Events:
UserAccountLinked(userId: DID,handle: Handle,linkedAt: Date) - Dispatched when a user successfully completes the OAuth flow for the first time.UserLoggedIn(userId: DID,loginAt: Date) - Dispatched on successful authentication/callback completion.
2. Application Layer#
Orchestrates the process of linking accounts and managing user data.
-
Use Cases / Application Services:
InitiateOAuthSignInUseCase: (May not be needed if the OAuth client library handles redirect generation directly in the Presentation layer). Takes a handle (optional) and returns the authorization URL to redirect the user to.CompleteOAuthSignInUseCase: Handles the logic after the user returns from the OAuth provider's callback URL.- Takes the callback parameters (code, state) as input.
- Uses an
IOAuthProcessor(infra interface) to validate the callback and obtain the authenticated user's session data (including DID and tokens). - Uses the
IUserRepositoryto find or create aUseraggregate based on the returned DID. - Updates user details (e.g.,
lastLoginAt, potentially fetches/updateshandle). - Saves the
Useraggregate viaIUserRepository. - Dispatches
UserAccountLinkedorUserLoggedInevents. - Returns a
UserOutputDTOor session identifier for the presentation layer.
GetUserUseCase: Retrieves aUserby their DID.GetCurrentUserUseCase: Retrieves theUserassociated with the current valid session. (Requires session context).
-
Data Transfer Objects (DTOs):
UserOutputDTO: Represents theUseraggregate data for external callers (id,handle,linkedAt,lastLoginAt).OAuthCallbackInputDTO: Contains parameters from the OAuth callback URL (code,state).CompleteOAuthSignInOutputDTO: ContainsUserOutputDTOand potentially status/session info.
-
Repository Interfaces:
IUserRepository: Interface for persisting and retrievingUseraggregates (findById(did),save(user)).
-
Infrastructure Service Interfaces: (Interfaces defined here, implemented in Infrastructure)
IOAuthProcessor: An abstraction over the@atproto/oauth-client-nodelibrary's core callback processing and session management logic. Methods likeprocessCallback(params)returning authenticated user DID and potentially session handle. This interface allows decoupling the use case from the specific library implementation.IOAuthSessionStore: Interface matching thesessionStorerequired by@atproto/oauth-client-node. Definesget(sub),set(sub, session),del(sub).IOAuthStateStore: Interface matching thestateStorerequired by@atproto/oauth-client-node. Definesget(key),set(key, state),del(key).
3. Infrastructure Layer#
Contains implementation details for persistence, OAuth handling, and external communication.
- Persistence:
UserRepository: ImplementsIUserRepositoryusing Drizzle/SQL, mapping theUseraggregate to auserstable.DrizzleOAuthSessionStore: ImplementsIOAuthSessionStoreusing Drizzle/SQL (or Redis, etc.) to store theSessiondata required by the OAuth client library, keyed by user DID (sub).DrizzleOAuthStateStore: ImplementsIOAuthStateStoreusing Drizzle/SQL (or Redis with TTL) to store temporary OAuth state data, keyed by the state parameter.
- OAuth Handling:
- Configuration and instantiation of
NodeOAuthClientfrom@atproto/oauth-client-node. - Provides the concrete implementations of
IOAuthSessionStoreandIOAuthStateStoreto theNodeOAuthClient. AtProtoOAuthProcessor: ImplementsIOAuthProcessor. This class wraps the configuredNodeOAuthClientinstance. ItsprocessCallbackmethod would call the underlyingclient.callback(params)method and handle mapping the result/errors.
- Configuration and instantiation of
- API / Presentation (e.g., Express.js):
/loginendpoint: Uses the configuredNodeOAuthClient(or a thin wrapper) to generate the authorization URL (client.authorize(...)) and redirects the user./atproto-oauth-callbackendpoint:- Receives the callback request from the browser.
- Extracts parameters (
code,state) from the URL. - Calls the
CompleteOAuthSignInUseCasewith the parameters. - The Use Case uses the
IOAuthProcessor(which usesclient.callback) to handle the OAuth exchange. Theclient.callbackinternally uses theIOAuthSessionStoreandIOAuthStateStoreimplementations. - The Use Case proceeds to find/create the
UserviaIUserRepository. - The endpoint handles the response from the Use Case (e.g., sets an application session cookie, redirects the user to their dashboard).
Interaction Flow (OAuth Callback Example)#
- User Redirected: User is redirected from Bluesky back to
/atproto-oauth-callback?code=...&state=.... - Controller: The Express route handler for
/atproto-oauth-callbackreceives the request. - Controller -> Use Case: The handler extracts
codeandstateand callsCompleteOAuthSignInUseCase.execute({ code, state }). - Use Case -> OAuth Processor: The use case calls
IOAuthProcessor.processCallback({ code, state }). - OAuth Processor -> OAuth Client Lib: The
AtProtoOAuthProcessorimplementation callsnodeOAuthClient.callback(params). - OAuth Client Lib -> Stores: The
nodeOAuthClientinteracts with the registeredIOAuthStateStore(to validate state) andIOAuthSessionStore(to save the new tokens/session keyed by the user's DID (sub)). - OAuth Processor -> Use Case: The
IOAuthProcessorreturns the authenticated user's DID (and potentially other session info) to the use case. - Use Case -> Repository: The use case calls
IUserRepository.findById(did). - Repository -> DB: The
UserRepositoryqueries the database. - Use Case: If user exists, updates
lastLoginAt. If not, creates a newUseraggregate instance with the DID, fetches handle (optional), setslinkedAt,lastLoginAt. - Use Case -> Repository: Calls
IUserRepository.save(user). - Repository -> DB: Inserts or updates the user record in the database.
- Use Case -> Event Dispatcher: Dispatches
UserAccountLinkedorUserLoggedIn. - Use Case -> Controller: Returns
UserOutputDTO(or session identifier). - Controller -> User: Sends response (e.g., redirect, set cookie).