feat: mint app delegations from the Internet Identity canister - #156
Open
sea-snake wants to merge 3 commits into
Open
feat: mint app delegations from the Internet Identity canister#156sea-snake wants to merge 3 commits into
sea-snake wants to merge 3 commits into
Conversation
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 23, 2026 22:18
15c5e9e to
3a4157d
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 23, 2026 23:05
3a4157d to
8966076
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 23, 2026 23:45
8966076 to
16c3f98
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 24, 2026 05:42
16c3f98 to
918eaa7
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 24, 2026 08:53
918eaa7 to
0f30d73
Compare
There was a problem hiding this comment.
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.tswith 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 (NoMatchingSession→SessionGoneError). - Adds
tests/client/session-minter.test.tsto 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.
sea-snake
force-pushed
the
feat/auth-session-minter
branch
2 times, most recently
from
August 24, 2026 10:38
abd041e to
f4753e2
Compare
MRmarioruci
reviewed
Aug 26, 2026
MRmarioruci
reviewed
Aug 26, 2026
MRmarioruci
approved these changes
Aug 26, 2026
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 10:23
a44360d to
fafe487
Compare
There was a problem hiding this comment.
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.sessionChainis documented as "Restricted to II", butcreate()actually validates the chain against the providedcanisterId. 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 tocanisterId, andcanisterIdis 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;
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 10:36
fafe487 to
8e3bc15
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 10:52
8e3bc15 to
62ecdfd
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 11:27
62ecdfd to
f647a60
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 11:59
f647a60 to
b1bf7ad
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 13:18
b1bf7ad to
39d256d
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 29, 2026 13:39
39d256d to
ac20bcf
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 31, 2026 08:52
ac20bcf to
249158a
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 31, 2026 09:34
249158a to
a56e82e
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
4 times, most recently
from
August 31, 2026 13:52
7c7000b to
24870a9
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
from
August 31, 2026 14:10
24870a9 to
89b0928
Compare
sea-snake
force-pushed
the
feat/auth-session-minter
branch
2 times, most recently
from
August 31, 2026 17:04
c282b64 to
f86d57c
Compare
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
force-pushed
the
feat/auth-session-minter
branch
from
August 31, 2026 17:53
f86d57c to
17045bc
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two calls behind the source the refreshing identity mints through:
app_prepare_delegation, thenapp_get_delegationwith 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.NoMatchingSessionends 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.targetsname 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.permissionsis refused with a named error — a read-only session is not something aDelegationChaincan 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.