Our Personal Data Server from scratch!
0

Configure Feed

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

fix(migrate): oauth account:* spec, p256 support

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 30, 2026, 9:07 PM +0300) commit 4e2525b2 parent 38508c2c change-id klqrnukm
+73 -3
+1 -1
frontend/public/oauth-client-metadata.json
··· 8 8 ], 9 9 "grant_types": ["authorization_code", "refresh_token"], 10 10 "response_types": ["code"], 11 - "scope": "atproto transition:generic repo:* blob:*/* rpc:* rpc:com.atproto.server.createAccount?aud=* account:*?action=manage identity:*", 11 + "scope": "atproto transition:generic repo:* blob:*/* rpc:* rpc:com.atproto.server.createAccount?aud=* account:*?action=manage account:repo?action=manage identity:*", 12 12 "token_endpoint_auth_method": "none", 13 13 "application_type": "web", 14 14 "dpop_bound_access_tokens": true
+6 -2
frontend/src/lib/migration/offline-flow.svelte.ts
··· 209 209 } 210 210 211 211 try { 212 - userRotationKeypair = await plcOps.getKeyPair(state.rotationKey.trim()); 213 212 const { lastOperation } = await plcOps.getLastPlcOpFromPlc(state.userDid); 214 213 const currentRotationKeys = lastOperation.rotationKeys || []; 215 214 216 - if (!currentRotationKeys.includes(userRotationKeypair.didPublicKey)) { 215 + userRotationKeypair = await plcOps.getMatchingKeyPair( 216 + state.rotationKey.trim(), 217 + currentRotationKeys, 218 + ); 219 + 220 + if (!userRotationKeypair) { 217 221 state.rotationKeyDidKey = ""; 218 222 return false; 219 223 }
+27
frontend/src/lib/migration/plc-ops.ts
··· 248 248 }; 249 249 } 250 250 251 + async getMatchingKeyPair( 252 + privateKeyString: string, 253 + acceptableDidKeys: readonly string[], 254 + ): Promise<KeypairInfo | null> { 255 + const curves: readonly KeyCurve[] = ["secp256k1", "p256"]; 256 + const results = await Promise.allSettled( 257 + curves.map((curve) => this.getKeyPair(privateKeyString, curve)), 258 + ); 259 + const candidates = results 260 + .filter( 261 + (r): r is PromiseFulfilledResult<KeypairInfo> => 262 + r.status === "fulfilled", 263 + ) 264 + .map((r) => r.value); 265 + if (candidates.length === 0) { 266 + const rejection = results.find( 267 + (r): r is PromiseRejectedResult => r.status === "rejected", 268 + ); 269 + throw rejection?.reason ?? new Error("Unrecognized key format"); 270 + } 271 + return ( 272 + candidates.find((info) => 273 + acceptableDidKeys.includes(info.didPublicKey) 274 + ) ?? null 275 + ); 276 + } 277 + 251 278 async signAndPublishNewOp( 252 279 did: string, 253 280 signingRotationKey: PrivateKey,
+39
frontend/src/tests/migration/plc-ops.test.ts
··· 144 144 }); 145 145 }); 146 146 147 + describe("getMatchingKeyPair", () => { 148 + it("resolves a P-256 key supplied as hex against its did:key", async () => { 149 + const keypair = await P256PrivateKeyExportable.createKeypair(); 150 + const rawHex = await keypair.exportPrivateKey("rawHex"); 151 + const did = await keypair.exportPublicKey("did"); 152 + 153 + const result = await plcOps.getMatchingKeyPair(rawHex, [did]); 154 + 155 + expect(result?.didPublicKey).toBe(did); 156 + }); 157 + 158 + it("resolves a secp256k1 key supplied as hex against its did:key", async () => { 159 + const keypair = await Secp256k1PrivateKeyExportable.createKeypair(); 160 + const rawHex = await keypair.exportPrivateKey("rawHex"); 161 + const did = await keypair.exportPublicKey("did"); 162 + 163 + const result = await plcOps.getMatchingKeyPair(rawHex, [did]); 164 + 165 + expect(result?.didPublicKey).toBe(did); 166 + }); 167 + 168 + it("returns null when no curve matches the accepted keys", async () => { 169 + const keypair = await P256PrivateKeyExportable.createKeypair(); 170 + const rawHex = await keypair.exportPrivateKey("rawHex"); 171 + 172 + const result = await plcOps.getMatchingKeyPair(rawHex, [ 173 + "did:key:zQ3shqPwo8CSE8zNXEyEpN4ASEBCCNeUFQq8Lrw3zkAJYB7SB", 174 + ]); 175 + 176 + expect(result).toBeNull(); 177 + }); 178 + 179 + it("throws on unparseable input", async () => { 180 + await expect( 181 + plcOps.getMatchingKeyPair("not-a-valid-key", []), 182 + ).rejects.toThrow(); 183 + }); 184 + }); 185 + 147 186 describe("getKeyPair - JWK format", () => { 148 187 it("imports secp256k1 JWK with d parameter", async () => { 149 188 const keypair = await Secp256k1PrivateKeyExportable.createKeypair();