feat(ccip-sdk): OIDC authentication for Canton Ledger API - #368
feat(ccip-sdk): OIDC authentication for Canton Ledger API#368SyedAsadKazmi wants to merge 8 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
0df3bc0 to
5cc96b5
Compare
This comment was marked as outdated.
This comment was marked as outdated.
5cc96b5 to
f1836b0
Compare
f1836b0 to
0e72327
Compare
0e72327 to
1de2ebb
Compare
1de2ebb to
3fd2a93
Compare
| const execution = await dest.execute({ | ||
| messageId: '0x1234...abcd', | ||
| wallet: cantonWallet, | ||
| wallet: { party: 'receiver::1220...' }, // Canton wallet (party ID, no signer needed) |
There was a problem hiding this comment.
Does this actually work? ccip-cli/src/providers/canton.ts has removed the wallet from loadCantonWallet entirely.
Also, this would be the same party as already specified in cantonConfig?
There was a problem hiding this comment.
You're right on both. The SDK example works (CantonChain.execute validates the wallet via isCantonWallet and uses wallet.party for actAs/payer), and the CLI's loadCantonWallet sources party entirely from cantonConfig.party , so it's the same value.
Fixed the example to extract cantonConfig into a variable and reuse cantonConfig.party instead of duplicating the string literal.
There was a problem hiding this comment.
Ah, I mixed up CLI and SDK. The SDK still accept and expects this wallet {party: 'asdf'}. Your change still looks good 👍
3fd2a93 to
b4b7fc0
Compare
b4b7fc0 to
855c73b
Compare
andrevmatos
left a comment
There was a problem hiding this comment.
Sorry, but we can't have this much node dependencies in the ccip-sdk;
I can see 2 usecases for this oauth worfklow for Canton: ccip-cli and an external provider (e.g. think of someone who manages to pack the Canton lib on their web-based or Electron-based app).
- For ccip-cli, this whole server-spawning and browser
opencall should be handled on the cli side, and then whatever result of that needed is then handed over thecantonConfig - For (web?) clients embedding this workflow, there may be an intersection of the above which they may be interested on, but they probably would implement their own callback REST endpoint, and/or some form of redirect on their application.
Strictly speaking, the SDK should be handed only what it needs, and these previous steps are mostly utilities. But the way I see this, we may be able to extract the reusable core intersection of the above to some functions which MAY be exported from the ccip-sdk, and implement the nodeJS-specific bits in the cli, maybe make providers/canton/ a folder.
| "rules": { | ||
| "ccip/restricted-syntax": "error", | ||
| "import/no-nodejs-modules": ["error", { "allow": ["buffer"] }] | ||
| "import/no-nodejs-modules": ["error", { "allow": ["buffer", "node:child_process", "node:http"] }] |
There was a problem hiding this comment.
No node modules on ccip-sdk, pls
|
Concrete proposal for the split: SDK — runtime-agnostic core ( CLI — Node orchestration ( Wiring: |
a1abac5 to
b334243
Compare
CI Test Report✅ 1585/1591 tests passed (392 suites) in 4m 50s SummaryCoverage report |
| /** | ||
| * A {@link TokenSource} that caches a token and lazily re-fetches via a | ||
| * caller-supplied `fetcher` when the cached token is expired or missing. | ||
| * | ||
| * The first `token()` call fetches; subsequent calls return the cached value | ||
| * until it expires, at which point a new fetch is triggered. Concurrent callers | ||
| * share a single in-flight fetch promise to avoid duplicate token requests. | ||
| */ | ||
| export class CachingTokenSource implements TokenSource { | ||
| private current: AccessToken | undefined | ||
| private readonly fetcher: () => Promise<AccessToken> | ||
| private inFlight: Promise<AccessToken> | undefined |
There was a problem hiding this comment.
I think this whole TokenSource thing can be replaced by a simple memoize call, with { async: true, expires: ... }; it already handles promises, returning the single inflight promise ootb, and removing the "cache" if promise rejects. You can also simply provide it a () => string in the static case
Adds OAuth 2.0 / OIDC auth for Canton, split across
ccip-sdk(runtime-agnostic protocol primitives) andccip-cli(Node-specific orchestration).SDK — runtime-agnostic core (
ccip-sdk/src/canton/authentication/):oauth4webapifor OAuth2 protocol mechanics (discovery, PKCE, grants, token validation) — purefetch/WebCrypto, nonode:*importscreateMemoizedTokenFetcher()— token caching with concurrent fetch coalescing and expiry-based invalidation, backed bymicro-memoize({ async: true })createAuthProvider()discriminated union selector (static / clientCredentials)buildAuthorizationRequest(),validateAuthorizationCallback(),exchangeAuthorizationCode(),refreshAuthorizationCodeToken()AuthorizationCodeProvider— caching provider wrapping caller-supplied fetch/refresh callbacksCantonConfig.jwtacceptsstring | (() => Promise<string>)— static token or per-request getter for refreshable tokensCANTON_AUTH_ERRORerror code + recovery hintCLI — Node orchestration (
ccip-cli/src/providers/canton/):auth.ts— localnode:httpcallback server,open/xdg-openbrowser launching,CANTON_CLIENT_ID/CANTON_CLIENT_SECRETenv-var resolution, process-wide auth provider cache (prevents double login acrosssend→showRequests)config.ts—loadCantonConfigacceptsauth;jwtoptional whenauthpresent. CLI resolvesauthupfront intojwt(string or getter) before handing config to the SDKwallet.ts— Ed25519 transaction signer + wallet loadingPromise.allSettledresults inspected to surfaceCCIPErrorinstead of genericRPC_NOT_FOUND