Skip to content

Commit c5bc50e

Browse files
committed
feat(auth): derive recap metadata for pre-generated PKP contexts
1 parent de1fa5a commit c5bc50e

File tree

8 files changed

+722
-102
lines changed

8 files changed

+722
-102
lines changed

packages/auth-helpers/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './lib/generate-auth-sig';
33
export * from './lib/models';
44
export * from './lib/recap/recap-session-capability-object';
55
export * from './lib/recap/resource-builder';
6+
export * from './lib/recap/utils';
67
export * from './lib/resources';
78
export * from './lib/session-capability-object';
89
export * from './lib/siwe/create-siwe-message';

packages/auth-helpers/src/lib/recap/utils.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,52 @@ export function getRecapNamespaceAndAbility(litAbility: LIT_ABILITY_VALUES): {
5454
);
5555
}
5656
}
57+
58+
export const RESOLVED_AUTH_CONTEXT_PREFIX = 'lit-resolvedauthcontext://';
59+
const PAYMENT_DELEGATION_PREFIX = 'lit-paymentdelegation://';
60+
const PKP_PREFIX = 'lit-pkp://';
61+
const ACC_PREFIX = 'lit-accesscontrolcondition://';
62+
63+
/**
64+
* Reverse mapping from Recap namespace/ability to LitAbility.
65+
* Returns null when the recap entry only carries metadata (eg. resolved auth context).
66+
*/
67+
export function getLitAbilityFromRecap(params: {
68+
recapNamespace: string;
69+
recapAbility: string;
70+
resourceKey: string;
71+
}): LIT_ABILITY_VALUES | null {
72+
const { recapNamespace, recapAbility, resourceKey } = params;
73+
74+
if (recapNamespace === LIT_NAMESPACE.Threshold) {
75+
if (recapAbility === LIT_RECAP_ABILITY.Decryption) {
76+
return LIT_ABILITY.AccessControlConditionDecryption;
77+
}
78+
79+
if (recapAbility === LIT_RECAP_ABILITY.Execution) {
80+
return LIT_ABILITY.LitActionExecution;
81+
}
82+
83+
if (recapAbility === LIT_RECAP_ABILITY.Signing) {
84+
if (resourceKey.startsWith(PKP_PREFIX)) {
85+
return LIT_ABILITY.PKPSigning;
86+
}
87+
if (resourceKey.startsWith(ACC_PREFIX)) {
88+
return LIT_ABILITY.AccessControlConditionSigning;
89+
}
90+
}
91+
}
92+
93+
if (recapNamespace === LIT_NAMESPACE.Auth && recapAbility === LIT_RECAP_ABILITY.Auth) {
94+
if (resourceKey.startsWith(PAYMENT_DELEGATION_PREFIX)) {
95+
return LIT_ABILITY.PaymentDelegation;
96+
}
97+
98+
if (resourceKey.startsWith(RESOLVED_AUTH_CONTEXT_PREFIX)) {
99+
// Resolved auth context entries only contain metadata.
100+
return null;
101+
}
102+
}
103+
104+
return null;
105+
}

packages/auth-helpers/src/lib/resources.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import {
99
import { AccessControlConditions, ILitResource } from '@lit-protocol/types';
1010
import { formatPKPResource } from './utils';
1111

12+
const RESOLVED_AUTH_CONTEXT_PREFIX = 'lit-resolvedauthcontext';
13+
type InternalResourcePrefix =
14+
| LIT_RESOURCE_PREFIX_VALUES
15+
| typeof RESOLVED_AUTH_CONTEXT_PREFIX;
16+
1217
abstract class LitResourceBase {
13-
abstract resourcePrefix: LIT_RESOURCE_PREFIX_VALUES;
18+
abstract resourcePrefix: InternalResourcePrefix;
1419
public readonly resource: string;
1520

1621
constructor(resource: string) {
@@ -158,6 +163,18 @@ export function parseLitResource(resourceKey: string): ILitResource {
158163
return new LitActionResource(
159164
resourceKey.substring(`${LIT_RESOURCE_PREFIX.LitAction}://`.length)
160165
);
166+
} else if (resourceKey.startsWith(RESOLVED_AUTH_CONTEXT_PREFIX)) {
167+
const resource = resourceKey.substring(
168+
`${RESOLVED_AUTH_CONTEXT_PREFIX}://`.length
169+
);
170+
171+
return {
172+
resourcePrefix: RESOLVED_AUTH_CONTEXT_PREFIX,
173+
resource,
174+
getResourceKey: () => `${RESOLVED_AUTH_CONTEXT_PREFIX}://${resource}`,
175+
toString: () => `${RESOLVED_AUTH_CONTEXT_PREFIX}://${resource}`,
176+
isValidLitAbility: () => false,
177+
} as unknown as ILitResource;
161178
}
162179
throw new InvalidArgumentException(
163180
{

packages/auth/src/lib/AuthManager/auth-manager.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,7 @@ export const createAuthManager = (authManagerParams: AuthManagerParams) => {
9090
delegationAuthSig: AuthSig;
9191
authData?: AuthData;
9292
}) => {
93-
return getPkpAuthContextFromPreGeneratedAdapter(
94-
authManagerParams,
95-
params
96-
);
93+
return getPkpAuthContextFromPreGeneratedAdapter(params);
9794
},
9895
createCustomAuthContext: (params: {
9996
// authData: AuthData;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { getPkpAuthContextFromPreGeneratedAdapter } from './getPkpAuthContextFromPreGeneratedAdapter';
2+
import type { AuthSig, SessionKeyPair } from '@lit-protocol/types';
3+
4+
const baseSessionKeyPair: SessionKeyPair = {
5+
publicKey: 'a1b2c3',
6+
secretKey: 'deadbeef',
7+
};
8+
9+
const baseDelegation = ({
10+
recap,
11+
}: {
12+
recap: Record<string, unknown>;
13+
}): AuthSig => {
14+
const resourcesLine = `- urn:recap:${Buffer.from(JSON.stringify(recap)).toString('base64url')}`;
15+
16+
const signedMessage = [
17+
'localhost wants you to sign in with your Ethereum account:',
18+
'0x1234567890abcdef1234567890ABCDEF12345678',
19+
'',
20+
"Lit Protocol PKP session signature I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Signing' for 'lit-pkp://*'.",
21+
'',
22+
'URI: lit:session:a1b2c3',
23+
'Version: 1',
24+
'Chain ID: 1',
25+
'Nonce: 0xabc',
26+
'Issued At: 2025-01-01T00:00:00Z',
27+
'Expiration Time: 2099-01-01T00:00:00.000Z',
28+
'Resources:',
29+
resourcesLine,
30+
].join('\n');
31+
32+
return {
33+
sig: '{"ProofOfPossession":"proof"}',
34+
algo: 'LIT_BLS',
35+
derivedVia: 'lit.bls',
36+
signedMessage,
37+
address: '0x1234567890abcdef1234567890ABCDEF12345678',
38+
};
39+
};
40+
41+
describe('getPkpAuthContextFromPreGeneratedAdapter', () => {
42+
it('derives authData from recap metadata when not provided', async () => {
43+
const recapPayload = {
44+
att: {
45+
'lit-pkp://*': {
46+
'Threshold/Signing': [{}],
47+
},
48+
'lit-resolvedauthcontext://*': {
49+
'Auth/Auth': [
50+
{
51+
auth_context: {
52+
authMethodContexts: [
53+
{
54+
appId: 'lit',
55+
authMethodType: 1,
56+
usedForSignSessionKeyRequest: true,
57+
userId: '0xabcdef',
58+
},
59+
],
60+
authSigAddress: null,
61+
},
62+
},
63+
],
64+
},
65+
},
66+
prf: [],
67+
};
68+
69+
const delegationAuthSig = baseDelegation({ recap: recapPayload });
70+
71+
const context = await getPkpAuthContextFromPreGeneratedAdapter({
72+
pkpPublicKey: '0xpkp',
73+
sessionKeyPair: baseSessionKeyPair,
74+
delegationAuthSig,
75+
});
76+
77+
expect(context.authData).toBeDefined();
78+
expect(context.authData?.authMethodId).toBe('0xabcdef');
79+
expect(context.derivedAuthMetadata?.authMethodId).toBe('0xabcdef');
80+
expect(context.authConfig.resources.length).toBeGreaterThan(0);
81+
});
82+
83+
it('throws when recap metadata is missing and authData not supplied', async () => {
84+
const recapPayload = {
85+
att: {
86+
'lit-pkp://*': {
87+
'Threshold/Signing': [{}],
88+
},
89+
},
90+
prf: [],
91+
};
92+
93+
const delegationAuthSig = baseDelegation({ recap: recapPayload });
94+
95+
await expect(
96+
getPkpAuthContextFromPreGeneratedAdapter({
97+
pkpPublicKey: '0xpkp',
98+
sessionKeyPair: baseSessionKeyPair,
99+
delegationAuthSig,
100+
})
101+
).rejects.toThrow(/Failed to derive authData/);
102+
});
103+
});

0 commit comments

Comments
 (0)