Skip to content

feat: mint app delegations from the Internet Identity canister - #156

Open
sea-snake wants to merge 3 commits into
feat/auth-session-mintingfrom
feat/auth-session-minter
Open

feat: mint app delegations from the Internet Identity canister#156
sea-snake wants to merge 3 commits into
feat/auth-session-mintingfrom
feat/auth-session-minter

Conversation

@sea-snake

@sea-snake sea-snake commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Two calls behind the source the refreshing identity mints through: app_prepare_delegation, then app_get_delegation with exactly the expiration prepare returned, since a different value is a different signature. The result is assembled into the one-hop chain an app signs with, rooted at the account's own key.

  • Only NoMatchingSession ends a session. A transport failure or an internal canister error leaves it alone and is worth retrying, so the unwrapping is where that distinction is made rather than at every call site.
  • A session chain is refused before the first call unless its targets name this canister and nothing else. The unrestricted case is the one worth refusing hardest: the session key signs with that chain, so accepting one would leave the library holding a credential good for any call rather than only the two made here.
  • A delegation carrying permissions is refused with a named error — a read-only session is not something a DelegationChain can represent, and sending one without the field would fail verification at the boundary node with nothing to point at.

The interface is written out rather than generated: this package carries no Internet Identity declarations, and generating them for three methods would mean carrying the whole interface.

Read hardest: assertChainReaches.

@sea-snake
sea-snake requested a review from a team as a code owner August 23, 2026 18:19
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 15c5e9e to 3a4157d Compare August 23, 2026 22:18
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 3a4157d to 8966076 Compare August 23, 2026 23:05
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 8966076 to 16c3f98 Compare August 23, 2026 23:45
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 16c3f98 to 918eaa7 Compare August 24, 2026 05:42
Copilot AI lite review requested due to automatic review settings August 24, 2026 08:53
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 918eaa7 to 0f30d73 Compare August 24, 2026 08:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a SessionMinter implementation that can mint app delegations by calling the Internet Identity canister session APIs, plus tests covering result/error classification and delegation-chain assembly/validation.

Changes:

  • Introduces src/client/session-minter.ts with a handwritten Candid interface for the three II session methods and logic to mint + assemble a one-hop app delegation chain.
  • Adds chain validation helpers (unwrapSessionResult, appDelegationChain, assertChainReaches) and session error classification (NoMatchingSessionSessionGoneError).
  • Adds tests/client/session-minter.test.ts to cover the error classification, chain assembly (including read-only refusal), and target/reachability checks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
tests/client/session-minter.test.ts New unit tests for SessionMinter helpers and delegation-chain behavior.
src/client/session-minter.ts New SessionMinter and helper functions + handwritten Candid factory for II session APIs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/client/session-minter.ts
Comment thread src/client/session-minter.ts
Comment thread src/client/session-minter.ts
Comment thread src/client/session-minter.ts
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch 2 times, most recently from abd041e to f4753e2 Compare August 24, 2026 10:38
Comment thread src/client/session-minter.ts Outdated
Comment thread src/client/session-minter.ts Outdated
@sea-snake
sea-snake changed the base branch from feat/auth-session-minting to main August 29, 2026 10:22
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from a44360d to fafe487 Compare August 29, 2026 10:23
@sea-snake
sea-snake changed the base branch from main to feat/auth-session-minting August 29, 2026 10:23
@sea-snake
sea-snake requested a lite review from Copilot August 29, 2026 10:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

src/client/session-minter.ts:102

  • SessionMinterOptions.sessionChain is documented as "Restricted to II", but create() actually validates the chain against the provided canisterId. This makes the doc misleading (and it’s not necessarily the mainnet II canister when using a local II). Update the comments to reflect the actual contract: the chain must be restricted to canisterId, and canisterId is the Internet Identity canister being called.
  /** The chain proving those calls come from the session. Restricted to II. */
  sessionChain: DelegationChain;

  /** The canister these calls go to. */
  canisterId: Principal;

Comment thread src/client/session-minter.ts Outdated
Comment thread src/client/session-minter.ts
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from fafe487 to 8e3bc15 Compare August 29, 2026 10:36
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 8e3bc15 to 62ecdfd Compare August 29, 2026 10:52
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 62ecdfd to f647a60 Compare August 29, 2026 11:27
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from f647a60 to b1bf7ad Compare August 29, 2026 11:59
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from b1bf7ad to 39d256d Compare August 29, 2026 13:18
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 39d256d to ac20bcf Compare August 29, 2026 13:39
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from ac20bcf to 249158a Compare August 31, 2026 08:52
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 249158a to a56e82e Compare August 31, 2026 09:34
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch 4 times, most recently from 7c7000b to 24870a9 Compare August 31, 2026 13:52
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from 24870a9 to 89b0928 Compare August 31, 2026 14:10
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch 2 times, most recently from c282b64 to f86d57c Compare August 31, 2026 17:04
Two calls behind the source the refreshing identity mints through:
prepare, then get with exactly the expiration prepare returned, since a
different value is a different signature. The result is assembled into
the one-hop chain an app signs with, rooted at the account's own key.

Only NoMatchingSession ends a session. A transport failure or an
internal canister error leaves it alone and is worth retrying, so the
unwrapping is where that distinction is made rather than at every call
site.

A session chain is refused before the first call unless its targets name
this canister and nothing else. The unrestricted case is the one worth
refusing hardest: the session key signs with that chain, so accepting
one would leave the library holding a credential good for any call
rather than only the two made here.

The interface is written out rather than generated: the package carries
no Internet Identity declarations, and generating them for three methods
would mean carrying the whole interface.
@sea-snake
sea-snake force-pushed the feat/auth-session-minter branch from f86d57c to 17045bc Compare August 31, 2026 17:53
Targets restrict the chain's final key, and the effective restriction is the
intersection of the hops that name any — so one restricting hop confines the
key and a hop naming nothing imposes nothing. Requiring every hop to restrict
refused the shape Internet Identity actually issues: the canister signs an
unrestricted hop to the provider's own key, and the provider's extension to the
application's key is what names the canister.

The message was the worse half. It printed the flattened targets, so a chain
refused for having an unrestricted hop reported `must be restricted to X, but
this one names X` — the same principal twice, with nothing to act on. Each
refusal now says which thing is wrong.

The fixture could not express the difference either: it folded an empty target
list into no targets at all, so the case the code cared about was untestable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants