mirror your GitHub repos to tangled.org automatically
1

Configure Feed

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

1import crypto from 'node:crypto' 2import { sql } from 'drizzle-orm' 3import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' 4import { installation, sshKey } from '../../server/db/schema' 5import { clearDb, setDb, useDb } from '../../server/utils/db' 6import { clearEncryptionKeyCache, decrypt } from '../../server/utils/encryption' 7import { generateAndPublishKey } from '../../server/utils/tangled-pubkey' 8import { createTestDb } from '../utils/db' 9 10const ORIGINAL_ENC_KEY = process.env.NUXT_ENCRYPTION_KEY 11 12const createRecordMock = vi.fn<(input: { repo: string, collection: string, record: Record<string, unknown> }) => Promise<{ data: { uri: string, cid: string } }>>() 13 14vi.mock('@atproto/api', () => ({ 15 Agent: class { 16 com = { 17 atproto: { 18 repo: { 19 createRecord: createRecordMock, 20 }, 21 }, 22 } 23 }, 24})) 25 26function fakeOauthSession(did: string) { 27 // The Agent mock above ignores its constructor argument, so we only need 28 // a `.did` field for the helper itself. 29 // eslint-disable-next-line ts/no-unsafe-type-assertion 30 return { did } as unknown as Parameters<typeof generateAndPublishKey>[0]['oauthSession'] 31} 32 33describe('generateAndPublishKey', () => { 34 beforeEach(async () => { 35 process.env.NUXT_ENCRYPTION_KEY = crypto.randomBytes(32).toString('base64') 36 clearEncryptionKeyCache() 37 38 setDb(await createTestDb()) 39 const db = useDb() 40 await db.insert(installation).values({ 41 id: 1, 42 accountLogin: 'alice', 43 accountId: 100, 44 accountType: 'User', 45 }) 46 47 createRecordMock.mockReset() 48 createRecordMock.mockResolvedValue({ 49 data: { uri: 'at://did:plc:abc/sh.tangled.publicKey/3kh2y4xq2lk2v', cid: 'bafy' }, 50 }) 51 }) 52 53 afterEach(() => { 54 if (ORIGINAL_ENC_KEY === undefined) delete process.env.NUXT_ENCRYPTION_KEY 55 else process.env.NUXT_ENCRYPTION_KEY = ORIGINAL_ENC_KEY 56 clearEncryptionKeyCache() 57 clearDb() 58 }) 59 60 it('generates a key, publishes to PDS, and stores the encrypted private half', async () => { 61 const result = await generateAndPublishKey({ 62 oauthSession: fakeOauthSession('did:plc:abc'), 63 installationId: 1, 64 }) 65 66 expect(result.created).toBe(true) 67 expect(createRecordMock).toHaveBeenCalledTimes(1) 68 const call = createRecordMock.mock.calls[0][0] 69 expect(call.repo).toBe('did:plc:abc') 70 expect(call.collection).toBe('sh.tangled.publicKey') 71 expect(call.record.$type).toBe('sh.tangled.publicKey') 72 expect(call.record.key).toMatch(/^ssh-ed25519 /) 73 expect(call.record.name).toBe('synchub.to/1') 74 75 const db = useDb() 76 const rows = await db.select().from(sshKey) 77 .where(sql`${sshKey.installationId} = 1 AND ${sshKey.did} = 'did:plc:abc'`) 78 expect(rows).toHaveLength(1) 79 const row = rows[0] 80 expect(row.publicKey).toMatch(/^ssh-ed25519 /) 81 expect(row.tangledKeyRkey).toBe('3kh2y4xq2lk2v') 82 83 const decrypted = decrypt(row.privateKeyCiphertext, row.privateKeyNonce) 84 expect(decrypted).toMatch(/^-----BEGIN PRIVATE KEY-----/) 85 expect(decrypted).toContain('-----END PRIVATE KEY-----') 86 }) 87 88 it('no-ops if a key already exists for (installation, did)', async () => { 89 await generateAndPublishKey({ 90 oauthSession: fakeOauthSession('did:plc:abc'), 91 installationId: 1, 92 }) 93 expect(createRecordMock).toHaveBeenCalledTimes(1) 94 95 const result = await generateAndPublishKey({ 96 oauthSession: fakeOauthSession('did:plc:abc'), 97 installationId: 1, 98 }) 99 expect(result.created).toBe(false) 100 expect(createRecordMock).toHaveBeenCalledTimes(1) // not called again 101 102 const db = useDb() 103 const rows = await db.select().from(sshKey) 104 expect(rows).toHaveLength(1) 105 }) 106 107 it('does not write a row if the PDS publish fails', async () => { 108 createRecordMock.mockRejectedValueOnce(new Error('pds is sad')) 109 110 await expect(generateAndPublishKey({ 111 oauthSession: fakeOauthSession('did:plc:abc'), 112 installationId: 1, 113 })).rejects.toThrow(/pds is sad/) 114 115 const db = useDb() 116 const rows = await db.select().from(sshKey) 117 expect(rows).toHaveLength(0) 118 }) 119})