This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: implement 401 error handling for authenticated agent retrieval

Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) <aider@aider.chat>

+101 -27
+9
src/modules/atproto/infrastructure/publishers/ATProtoCardPublisher.ts
··· 8 8 import { IAgentService } from '../../application/IAgentService'; 9 9 import { DID } from '../../domain/DID'; 10 10 import { PublishedRecordId } from 'src/modules/cards/domain/value-objects/PublishedRecordId'; 11 + import { AuthenticationError } from 'src/shared/core/AuthenticationError'; 11 12 export class ATProtoCardPublisher implements ICardPublisher { 12 13 constructor( 13 14 private readonly agentService: IAgentService, ··· 45 46 await this.agentService.getAuthenticatedAgent(curatorDid); 46 47 47 48 if (agentResult.isErr()) { 49 + // Propagate authentication errors as-is 50 + if (agentResult.error instanceof AuthenticationError) { 51 + return err(agentResult.error); 52 + } 48 53 return err( 49 54 new Error( 50 55 `Authentication error for ATProtoCardPublisher: ${agentResult.error.message}`, ··· 131 136 await this.agentService.getAuthenticatedAgent(curatorDid); 132 137 133 138 if (agentResult.isErr()) { 139 + // Propagate authentication errors as-is 140 + if (agentResult.error instanceof AuthenticationError) { 141 + return err(agentResult.error); 142 + } 134 143 return err( 135 144 new Error( 136 145 `Authentication error for ATProtoCardPublisher: ${agentResult.error.message}`,
+20 -3
src/modules/atproto/infrastructure/publishers/ATProtoCollectionPublisher.ts
··· 13 13 import { StrongRef } from '../../domain'; 14 14 import { IAgentService } from '../../application/IAgentService'; 15 15 import { DID } from '../../domain/DID'; 16 + import { AuthenticationError } from 'src/shared/core/AuthenticationError'; 16 17 17 18 export class ATProtoCollectionPublisher implements ICollectionPublisher { 18 19 constructor( ··· 43 44 await this.agentService.getAuthenticatedAgent(curatorDid); 44 45 45 46 if (agentResult.isErr()) { 47 + // Propagate authentication errors as-is 48 + if (agentResult.error instanceof AuthenticationError) { 49 + return err(agentResult.error); 50 + } 46 51 return err( 47 52 new Error( 48 53 `Authentication error for ATProtoCollectionPublisher: ${agentResult.error.message}`, ··· 125 130 await this.agentService.getAuthenticatedAgent(curatorDid); 126 131 127 132 if (agentResult.isErr()) { 133 + // Propagate authentication errors as-is 134 + if (agentResult.error instanceof AuthenticationError) { 135 + return err(agentResult.error); 136 + } 128 137 return err( 129 138 new Error( 130 - `Authenticated error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 139 + `Authentication error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 131 140 ), 132 141 ); 133 142 } ··· 225 234 await this.agentService.getAuthenticatedAgent(curatorDid); 226 235 227 236 if (agentResult.isErr()) { 237 + // Propagate authentication errors as-is 238 + if (agentResult.error instanceof AuthenticationError) { 239 + return err(agentResult.error); 240 + } 228 241 return err( 229 242 new Error( 230 - `Authenticated error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 243 + `Authentication error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 231 244 ), 232 245 ); 233 246 } ··· 271 284 await this.agentService.getAuthenticatedAgent(curatorDid); 272 285 273 286 if (agentResult.isErr()) { 287 + // Propagate authentication errors as-is 288 + if (agentResult.error instanceof AuthenticationError) { 289 + return err(agentResult.error); 290 + } 274 291 return err( 275 292 new Error( 276 - `Authenticated error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 293 + `Authentication error for ATProtoCollectionPublisher: ${agentResult.error.message}`, 277 294 ), 278 295 ); 279 296 }
+13 -15
src/modules/atproto/infrastructure/services/ATProtoAgentService.ts
··· 5 5 import { DID } from '../../domain/DID'; 6 6 import { IAppPasswordSessionService } from '../../application/IAppPasswordSessionService'; 7 7 import { ATPROTO_SERVICE_ENDPOINTS } from './ServiceEndpoints'; 8 + import { AuthenticationError } from 'src/shared/core/AuthenticationError'; 8 9 9 10 export class ATProtoAgentService implements IAgentService { 10 11 constructor( ··· 27 28 await this.getAuthenticatedAgentByAppPasswordSession(did); 28 29 if (appPasswordAgentResult.isErr()) { 29 30 return err( 30 - new Error( 31 - `Failed to get authenticated agent: ${oauthAgentResult.error.message} | ${appPasswordAgentResult.error.message}`, 31 + new AuthenticationError( 32 + `Failed to authenticate: No valid OAuth or App Password session found`, 32 33 ), 33 34 ); 34 35 } ··· 49 50 } 50 51 51 52 // No session found 52 - throw new Error('No session found for the provided DID'); 53 + throw new AuthenticationError('No OAuth session found for the provided DID'); 53 54 } catch (error) { 54 55 return err( 55 - new Error( 56 - `Failed to get authenticated agent by OAuth session: ${error instanceof Error ? error.message : String(error)}`, 57 - ), 56 + error instanceof AuthenticationError 57 + ? error 58 + : new AuthenticationError(`OAuth authentication failed: ${error instanceof Error ? error.message : String(error)}`) 58 59 ); 59 60 } 60 61 } ··· 67 68 await this.appPasswordSessionService.getSession(did.value); 68 69 69 70 if (appPasswordSessionResult.isErr()) { 70 - return err( 71 - new Error( 72 - `Failed to get App Password session: ${appPasswordSessionResult.error.message}`, 73 - ), 74 - ); 71 + return err(new AuthenticationError(`App Password session failed: ${appPasswordSessionResult.error.message}`)); 75 72 } 73 + 76 74 const session = appPasswordSessionResult.value; 77 75 if (session) { 78 76 // Create an Agent with the session ··· 88 86 } 89 87 90 88 // No session found 91 - throw new Error('No session found for the provided DID'); 89 + throw new AuthenticationError('No App Password session found for the provided DID'); 92 90 } catch (error) { 93 91 return err( 94 - new Error( 95 - `Failed to get authenticated agent by App Password session: ${error instanceof Error ? error.message : String(error)}`, 96 - ), 92 + error instanceof AuthenticationError 93 + ? error 94 + : new AuthenticationError(`App Password authentication failed: ${error instanceof Error ? error.message : String(error)}`) 97 95 ); 98 96 } 99 97 }
+15 -2
src/modules/cards/application/useCases/commands/AddUrlToLibraryUseCase.ts
··· 17 17 import { CardLibraryService } from '../../../domain/services/CardLibraryService'; 18 18 import { CardCollectionService } from '../../../domain/services/CardCollectionService'; 19 19 import { CardContent } from '../../../domain/value-objects/CardContent'; 20 + import { AuthenticationError } from '../../../../../shared/core/AuthenticationError'; 20 21 21 22 export interface AddUrlToLibraryDTO { 22 23 url: string; ··· 38 39 39 40 export class AddUrlToLibraryUseCase extends BaseUseCase< 40 41 AddUrlToLibraryDTO, 41 - Result<AddUrlToLibraryResponseDTO, ValidationError | AppError.UnexpectedError> 42 + Result<AddUrlToLibraryResponseDTO, ValidationError | AuthenticationError | AppError.UnexpectedError> 42 43 > { 43 44 constructor( 44 45 private cardRepository: ICardRepository, ··· 55 56 ): Promise< 56 57 Result< 57 58 AddUrlToLibraryResponseDTO, 58 - ValidationError | AppError.UnexpectedError 59 + ValidationError | AuthenticationError | AppError.UnexpectedError 59 60 > 60 61 > { 61 62 try { ··· 129 130 const addUrlCardToLibraryResult = 130 131 await this.cardLibraryService.addCardToLibrary(urlCard, curatorId); 131 132 if (addUrlCardToLibraryResult.isErr()) { 133 + // Propagate authentication errors 134 + if (addUrlCardToLibraryResult.error instanceof AuthenticationError) { 135 + return err(addUrlCardToLibraryResult.error); 136 + } 132 137 if ( 133 138 addUrlCardToLibraryResult.error instanceof AppError.UnexpectedError 134 139 ) { ··· 210 215 const addNoteCardToLibraryResult = 211 216 await this.cardLibraryService.addCardToLibrary(noteCard, curatorId); 212 217 if (addNoteCardToLibraryResult.isErr()) { 218 + // Propagate authentication errors 219 + if (addNoteCardToLibraryResult.error instanceof AuthenticationError) { 220 + return err(addNoteCardToLibraryResult.error); 221 + } 213 222 if ( 214 223 addNoteCardToLibraryResult.error instanceof 215 224 AppError.UnexpectedError ··· 254 263 curatorId, 255 264 ); 256 265 if (addToCollectionsResult.isErr()) { 266 + // Propagate authentication errors 267 + if (addToCollectionsResult.error instanceof AuthenticationError) { 268 + return err(addToCollectionsResult.error); 269 + } 257 270 if ( 258 271 addToCollectionsResult.error instanceof AppError.UnexpectedError 259 272 ) {
+13 -4
src/modules/cards/domain/services/CardCollectionService.ts
··· 7 7 import { ICollectionPublisher } from '../../application/ports/ICollectionPublisher'; 8 8 import { AppError } from '../../../../shared/core/AppError'; 9 9 import { DomainService } from '../../../../shared/domain/DomainService'; 10 + import { AuthenticationError } from '../../../../shared/core/AuthenticationError'; 10 11 11 12 export class CardCollectionValidationError extends Error { 12 13 constructor(message: string) { ··· 26 27 collectionId: CollectionId, 27 28 curatorId: CuratorId, 28 29 ): Promise< 29 - Result<Collection, CardCollectionValidationError | AppError.UnexpectedError> 30 + Result<Collection, CardCollectionValidationError | AuthenticationError | AppError.UnexpectedError> 30 31 > { 31 32 try { 32 33 // Find the collection ··· 63 64 curatorId, 64 65 ); 65 66 if (publishLinkResult.isErr()) { 67 + // Propagate authentication errors 68 + if (publishLinkResult.error instanceof AuthenticationError) { 69 + return err(publishLinkResult.error); 70 + } 66 71 return err( 67 72 new CardCollectionValidationError( 68 73 `Failed to publish collection link: ${publishLinkResult.error.message}`, ··· 93 98 ): Promise< 94 99 Result< 95 100 Collection[], 96 - CardCollectionValidationError | AppError.UnexpectedError 101 + CardCollectionValidationError | AuthenticationError | AppError.UnexpectedError 97 102 > 98 103 > { 99 104 const updatedCollections: Collection[] = []; ··· 119 124 ): Promise< 120 125 Result< 121 126 Collection | null, 122 - CardCollectionValidationError | AppError.UnexpectedError 127 + CardCollectionValidationError | AuthenticationError | AppError.UnexpectedError 123 128 > 124 129 > { 125 130 try { ··· 155 160 cardLink.publishedRecordId, 156 161 ); 157 162 if (unpublishLinkResult.isErr()) { 163 + // Propagate authentication errors 164 + if (unpublishLinkResult.error instanceof AuthenticationError) { 165 + return err(unpublishLinkResult.error); 166 + } 158 167 return err( 159 168 new CardCollectionValidationError( 160 169 `Failed to unpublish collection link: ${unpublishLinkResult.error.message}`, ··· 193 202 ): Promise< 194 203 Result< 195 204 Collection[], 196 - CardCollectionValidationError | AppError.UnexpectedError 205 + CardCollectionValidationError | AuthenticationError | AppError.UnexpectedError 197 206 > 198 207 > { 199 208 const updatedCollections: Collection[] = [];
+11 -2
src/modules/cards/domain/services/CardLibraryService.ts
··· 8 8 import { DomainService } from '../../../../shared/domain/DomainService'; 9 9 import { CardCollectionService } from './CardCollectionService'; 10 10 import { PublishedRecordId } from '../value-objects/PublishedRecordId'; 11 + import { AuthenticationError } from '../../../../shared/core/AuthenticationError'; 11 12 12 13 export class CardLibraryValidationError extends Error { 13 14 constructor(message: string) { ··· 28 29 card: Card, 29 30 curatorId: CuratorId, 30 31 ): Promise< 31 - Result<Card, CardLibraryValidationError | AppError.UnexpectedError> 32 + Result<Card, CardLibraryValidationError | AuthenticationError | AppError.UnexpectedError> 32 33 > { 33 34 try { 34 35 // Check if card is already in curator's library ··· 76 77 parentCardPublishedRecordId, 77 78 ); 78 79 if (publishResult.isErr()) { 80 + // Propagate authentication errors 81 + if (publishResult.error instanceof AuthenticationError) { 82 + return err(publishResult.error); 83 + } 79 84 return err( 80 85 new CardLibraryValidationError( 81 86 `Failed to publish card to library: ${publishResult.error.message}`, ··· 112 117 card: Card, 113 118 curatorId: CuratorId, 114 119 ): Promise< 115 - Result<Card, CardLibraryValidationError | AppError.UnexpectedError> 120 + Result<Card, CardLibraryValidationError | AuthenticationError | AppError.UnexpectedError> 116 121 > { 117 122 try { 118 123 // Check if card is in curator's library ··· 190 195 libraryInfo.curatorId, 191 196 ); 192 197 if (unpublishResult.isErr()) { 198 + // Propagate authentication errors 199 + if (unpublishResult.error instanceof AuthenticationError) { 200 + return err(unpublishResult.error); 201 + } 193 202 return err( 194 203 new CardLibraryValidationError( 195 204 `Failed to unpublish card from library: ${unpublishResult.error.message}`,
+6 -1
src/modules/cards/infrastructure/http/controllers/AddUrlToLibraryController.ts
··· 2 2 import { Response } from 'express'; 3 3 import { AddUrlToLibraryUseCase } from '../../../application/useCases/commands/AddUrlToLibraryUseCase'; 4 4 import { AuthenticatedRequest } from '../../../../../shared/infrastructure/http/middleware/AuthMiddleware'; 5 + import { AuthenticationError } from '../../../../../shared/core/AuthenticationError'; 5 6 6 7 export class AddUrlToLibraryController extends Controller { 7 8 constructor(private addUrlToLibraryUseCase: AddUrlToLibraryUseCase) { ··· 29 30 }); 30 31 31 32 if (result.isErr()) { 33 + // Check if the error is an authentication error 34 + if (result.error instanceof AuthenticationError) { 35 + return this.unauthorized(res, result.error.message); 36 + } 32 37 return this.fail(res, result.error); 33 38 } 34 39 35 40 return this.ok(res, result.value); 36 41 } catch (error: any) { 37 - return this.fail(res, error); 42 + return this.handleError(res, error); 38 43 } 39 44 } 40 45 }
+6
src/shared/core/AuthenticationError.ts
··· 1 + export class AuthenticationError extends Error { 2 + constructor(message: string) { 3 + super(message); 4 + this.name = 'AuthenticationError'; 5 + } 6 + }
+8
src/shared/infrastructure/http/Controller.ts
··· 1 1 import { Response } from 'express'; 2 + import { AuthenticationError } from '../../core/AuthenticationError'; 2 3 3 4 export abstract class Controller { 4 5 protected abstract executeImpl(req: any, res: Response): Promise<any>; ··· 62 63 return res.status(500).json({ 63 64 message: error.toString(), 64 65 }); 66 + } 67 + 68 + protected handleError(res: Response, error: Error): Response { 69 + if (error instanceof AuthenticationError) { 70 + return this.unauthorized(res, error.message); 71 + } 72 + return this.fail(res, error); 65 73 } 66 74 }