WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)

Add authentication validation helper

Extracts duplicated auth validation logic into reusable utility:
- requireAuth(): Validates client is authenticated and has active session
- Throws clear error messages if not authenticated
- Returns session with DID and handle

This helper will be used across all API operations that require
authentication, ensuring consistent error messages and reducing
code duplication.

Includes test coverage (3 tests passing)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

markbennett.ca 528dbeac 70d08ef4

verified
+60
+22
src/utils/auth-helpers.ts
··· 1 + import type { TangledApiClient } from '../lib/api-client.js'; 2 + 3 + /** 4 + * Validate that the client is authenticated and has an active session 5 + * @throws Error if not authenticated or no session found 6 + * @returns The current session with did and handle 7 + */ 8 + export async function requireAuth(client: TangledApiClient): Promise<{ 9 + did: string; 10 + handle: string; 11 + }> { 12 + if (!(await client.isAuthenticated())) { 13 + throw new Error('Must be authenticated. Run "tangled auth login" first.'); 14 + } 15 + 16 + const session = client.getSession(); 17 + if (!session) { 18 + throw new Error('No active session found'); 19 + } 20 + 21 + return session; 22 + }
+38
tests/utils/auth-helpers.test.ts
··· 1 + import { beforeEach, describe, expect, it, vi } from 'vitest'; 2 + import { requireAuth } from '../../src/utils/auth-helpers.js'; 3 + import type { TangledApiClient } from '../../src/lib/api-client.js'; 4 + 5 + // Mock API client factory 6 + const createMockClient = (authenticated: boolean, session: { did: string; handle: string } | null): TangledApiClient => { 7 + return { 8 + isAuthenticated: vi.fn(async () => authenticated), 9 + getSession: vi.fn(() => session), 10 + } as unknown as TangledApiClient; 11 + }; 12 + 13 + describe('requireAuth', () => { 14 + it('should return session when authenticated', async () => { 15 + const mockSession = { did: 'did:plc:test123', handle: 'test.bsky.social' }; 16 + const mockClient = createMockClient(true, mockSession); 17 + 18 + const result = await requireAuth(mockClient); 19 + 20 + expect(result).toEqual(mockSession); 21 + }); 22 + 23 + it('should throw error when not authenticated', async () => { 24 + const mockClient = createMockClient(false, null); 25 + 26 + await expect(requireAuth(mockClient)).rejects.toThrow( 27 + 'Must be authenticated. Run "tangled auth login" first.', 28 + ); 29 + }); 30 + 31 + it('should throw error when authenticated but no session', async () => { 32 + const mockClient = createMockClient(true, null); 33 + 34 + await expect(requireAuth(mockClient)).rejects.toThrow( 35 + 'No active session found', 36 + ); 37 + }); 38 + });