Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
a60eb4a
feat(be): cap sessions per identity
sea-snake Aug 22, 2026
34ce16a
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 25, 2026
aadcc99
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 25, 2026
cdc3069
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 25, 2026
16475ba
Merge branch 'feat/session-device-key-proof' into feat/session-create
sea-snake Aug 31, 2026
3a327ae
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 31, 2026
3d0833c
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 31, 2026
ef0316e
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 31, 2026
4bc15ec
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Aug 31, 2026
93beaef
merge: one predicate, and a default idle bound
sea-snake Aug 31, 2026
97ade0b
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 1, 2026
e63f288
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 1, 2026
6a710de
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 1, 2026
25d2b86
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 1, 2026
f3aec2c
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
ba3b00a
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
ebb8ba8
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
2bf6696
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
7211306
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
fc3105e
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
fa25cce
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 4, 2026
d51efa2
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
ac9f482
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
fc072d5
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
e4c3b51
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
e0eff6f
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
92ccdf0
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
127098c
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
67b934f
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
36d7f47
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
dd7c842
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
b4c27c0
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
012070d
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
d8f4bfe
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
b0aabe8
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
ad25a51
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
67c24db
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
d1884e6
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
985c2cb
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
fce16f2
refactor(be): name the account reference list rather than a row
sea-snake Sep 5, 2026
66e464d
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 5, 2026
f0c7c02
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
bea2432
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
9c4520c
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
9e87002
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
db44345
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
26fb487
refactor(be): a browser, not a session device
sea-snake Sep 6, 2026
48fa31a
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
84bba71
test(be): restore two tests a merge silently deleted
sea-snake Sep 6, 2026
dda08dc
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
bb6e9dc
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
b9d663c
fix(be): store the anchor the write path was handed, always
sea-snake Sep 6, 2026
850c996
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
08283a5
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 6, 2026
7649ba0
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
3b93122
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
057f454
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
ad84c7b
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
de03e4b
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
1860f26
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
1c1d44e
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
c051fc3
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
19bd01f
fix(internet_identity): the session cap refuses growth, not every move
sea-snake Sep 7, 2026
d7b64cd
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
4b35ed2
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
c816a6e
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
25f94ef
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 7, 2026
d403a93
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 8, 2026
d3e06c5
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 8, 2026
943d540
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 8, 2026
8ba87e6
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 8, 2026
0b5876b
chore: merge feat/session-create
sea-snake Sep 8, 2026
712c7e1
chore: merge feat/session-create
sea-snake Sep 8, 2026
e845284
chore: merge feat/session-create
sea-snake Sep 8, 2026
93b8c50
chore: merge feat/session-create
sea-snake Sep 8, 2026
e2a39ff
chore: merge feat/session-create
sea-snake Sep 8, 2026
6277d0b
chore: merge feat/session-create
sea-snake Sep 8, 2026
4375096
chore: merge feat/session-create
sea-snake Sep 9, 2026
54fa5f7
chore: merge feat/session-create
sea-snake Sep 9, 2026
bdd4d47
fix(be): the write gate reclaims sessions, and counts them in i64
sea-snake Sep 9, 2026
0e945b1
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 9, 2026
3a3539c
chore: rename the session record accessors at their call sites
sea-snake Sep 9, 2026
a7d0b8d
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 9, 2026
9c6abda
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 9, 2026
246e75e
Merge branch 'feat/session-create' into feat/session-cap-per-identity
sea-snake Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 135 additions & 27 deletions src/internet_identity/src/account_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub fn set_default_account_for_origin(
anchor_number: AnchorNumber,
origin: FrontendHostname,
account_number: Option<AccountNumber>,
now: Timestamp,
) -> Result<AccountInfo, SetDefaultAccountError> {
let account = try_read_account_info(anchor_number, &origin, account_number).map_err(|_| {
SetDefaultAccountError::NoSuchAccount {
Expand All @@ -149,7 +150,7 @@ pub fn set_default_account_for_origin(
})?;

storage_borrow_mut(|storage| {
storage.set_default_account(anchor_number, origin, account_number)
storage.set_default_account(anchor_number, origin, account_number, now)
})
.map_err(|err| SetDefaultAccountError::InternalCanisterError(err.to_string()))?;

Expand All @@ -160,11 +161,12 @@ pub fn create_account_for_origin(
anchor_number: AnchorNumber,
origin: FrontendHostname,
name: String,
now: Timestamp,
) -> Result<Account, CreateAccountError> {
validate_account_name(&name).map_err(Into::<CreateAccountError>::into)?;
let created_account = storage_borrow_mut(|storage| {
storage
.create_account(anchor_number, origin, name.clone())
.create_account(anchor_number, origin, name.clone(), now)
.map_err(|err| match err {
// The cap is the write path's rule, so it says so rather than the caller
// asking first — and naming a tracked default reaches it the same way,
Expand All @@ -189,6 +191,7 @@ pub fn update_account_for_origin(
account_number: Option<AccountNumber>,
origin: FrontendHostname,
update: AccountUpdate,
now: Timestamp,
) -> Result<Account, UpdateAccountError> {
match update.name {
Some(new_name) => {
Expand All @@ -212,7 +215,7 @@ pub fn update_account_for_origin(
let mut renamed_account = old_account.clone();
renamed_account.name = Some(new_name.clone());
let updated_account = storage
.write_account(renamed_account)
.write_account(renamed_account, now)
.map_err(|err| match err {
StorageError::AccountLimitReached { .. } => {
UpdateAccountError::AccountLimitReached
Expand Down Expand Up @@ -307,7 +310,7 @@ pub fn prepare_account_delegation(
storage_borrow_mut(|storage| {
let mut used_account = account;
used_account.last_used = Some(now);
storage.write_account(used_account)
storage.write_account(used_account, now)
})
.map_err(|err| AccountDelegationError::InternalCanisterError(err.to_string()))?;

Expand Down Expand Up @@ -409,7 +412,12 @@ fn should_create_account_for_origin() {
let name = "Alice".to_string();

assert_eq!(
create_account_for_origin(anchor.anchor_number(), origin.clone(), name.clone()),
create_account_for_origin(
anchor.anchor_number(),
origin.clone(),
name.clone(),
crate::storage::TEST_NOW
),
Ok(Account::new_full(
anchor.anchor_number(),
origin,
Expand All @@ -435,8 +443,12 @@ fn should_fail_to_create_accounts_above_max() {
let name = "Alice".to_string();
for i in 0..=MAX_ANCHOR_ACCOUNTS {
let origin = format!("https://example-{i}.com");
let result =
create_account_for_origin(anchor.anchor_number(), origin.clone(), name.clone());
let result = create_account_for_origin(
anchor.anchor_number(),
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);
if i == MAX_ANCHOR_ACCOUNTS {
assert_eq!(result, Err(CreateAccountError::AccountLimitReached))
} else {
Expand All @@ -459,8 +471,12 @@ fn should_fail_to_update_default_accounts_above_max() {
let name = "Alice".to_string();
for i in 0..MAX_ANCHOR_ACCOUNTS {
let origin = format!("https://example-{i}.com");
let create_result =
create_account_for_origin(anchor.anchor_number(), origin.clone(), name.clone());
let create_result = create_account_for_origin(
anchor.anchor_number(),
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);

assert!(create_result.is_ok())
}
Expand All @@ -471,6 +487,7 @@ fn should_fail_to_update_default_accounts_above_max() {
AccountUpdate {
name: Some("Gabriel".to_string()),
},
crate::storage::TEST_NOW,
);
assert_eq!(result, Err(UpdateAccountError::AccountLimitReached))
}
Expand All @@ -490,8 +507,18 @@ fn should_get_accounts_for_origin() {
let name_two = "Bob".to_string();
let anchor_number = anchor.anchor_number();

let _ = create_account_for_origin(anchor_number, origin.clone(), name.clone());
let _ = create_account_for_origin(anchor_number, origin.clone(), name_two.clone());
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name_two.clone(),
crate::storage::TEST_NOW,
);

assert_eq!(
get_accounts_for_origin(anchor_number, &origin),
Expand Down Expand Up @@ -538,8 +565,18 @@ fn should_only_get_own_accounts_for_origin() {
let anchor_number = anchor.anchor_number();
let anchor_number_two = anchor_two.anchor_number();

let _ = create_account_for_origin(anchor_number, origin.clone(), name.clone());
let _ = create_account_for_origin(anchor_number_two, origin.clone(), name_two.clone());
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);
let _ = create_account_for_origin(
anchor_number_two,
origin.clone(),
name_two.clone(),
crate::storage::TEST_NOW,
);

assert_eq!(
get_accounts_for_origin(anchor_number, &origin),
Expand Down Expand Up @@ -587,8 +624,18 @@ fn should_update_account_for_origin() {
let name_two = "Bob".to_string();
let anchor_number = anchor.anchor_number();

let _ = create_account_for_origin(anchor_number, origin.clone(), name.clone());
let _ = create_account_for_origin(anchor_number, origin.clone(), name_two.clone());
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name_two.clone(),
crate::storage::TEST_NOW,
);

assert_eq!(
get_accounts_for_origin(anchor_number, &origin),
Expand Down Expand Up @@ -620,7 +667,8 @@ fn should_update_account_for_origin() {
origin.clone(),
AccountUpdate {
name: Some("Becky".to_string())
}
},
crate::storage::TEST_NOW
),
Ok(Account::new_full(
anchor_number,
Expand Down Expand Up @@ -671,8 +719,18 @@ fn should_update_default_account_for_origin() {
let name_two = "Bob".to_string();
let anchor_number = anchor.anchor_number();

let _ = create_account_for_origin(anchor_number, origin.clone(), name.clone());
let _ = create_account_for_origin(anchor_number, origin.clone(), name_two.clone());
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name.clone(),
crate::storage::TEST_NOW,
);
let _ = create_account_for_origin(
anchor_number,
origin.clone(),
name_two.clone(),
crate::storage::TEST_NOW,
);

assert_eq!(
get_accounts_for_origin(anchor_number, &origin),
Expand Down Expand Up @@ -704,7 +762,8 @@ fn should_update_default_account_for_origin() {
origin.clone(),
AccountUpdate {
name: Some("Becky".to_string())
}
},
crate::storage::TEST_NOW
),
Ok(Account::new_full(
anchor_number,
Expand Down Expand Up @@ -759,7 +818,13 @@ fn naming_a_tracked_default_at_the_account_limit_is_refused() {
anchor
});
let origin = "https://example.com".to_string();
create_account_for_origin(anchor.anchor_number(), origin.clone(), "first".to_string()).unwrap();
create_account_for_origin(
anchor.anchor_number(),
origin.clone(),
"first".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();

// At the limit, and naming another account is the one thing that cannot be done —
// said by the write itself rather than by a caller asking first.
Expand All @@ -772,7 +837,12 @@ fn naming_a_tracked_default_at_the_account_limit_is_refused() {
});

assert_eq!(
create_account_for_origin(anchor.anchor_number(), origin, "second".to_string()),
create_account_for_origin(
anchor.anchor_number(),
origin,
"second".to_string(),
crate::storage::TEST_NOW
),
Err(CreateAccountError::AccountLimitReached)
);
}
Expand Down Expand Up @@ -804,6 +874,7 @@ fn a_drifted_account_counter_is_not_repaired_and_costs_the_identity_its_limit()
anchor.anchor_number(),
"https://example.com".to_string(),
name,
crate::storage::TEST_NOW,
),
Err(CreateAccountError::AccountLimitReached)
);
Expand All @@ -822,8 +893,20 @@ fn should_get_default_account_for_origin() {
let origin = "https://example.com".to_string();
let anchor_number = anchor.anchor_number();

create_account_for_origin(anchor_number, origin.clone(), "Alice".to_string()).unwrap();
create_account_for_origin(anchor_number, origin.clone(), "Bob".to_string()).unwrap();
create_account_for_origin(
anchor_number,
origin.clone(),
"Alice".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();
create_account_for_origin(
anchor_number,
origin.clone(),
"Bob".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();

// Smoke test
assert_eq!(
Expand Down Expand Up @@ -953,6 +1036,7 @@ fn should_get_default_account_for_origin() {
anchor_number,
origin.clone(),
default_account_number,
crate::storage::TEST_NOW,
);

assert_eq!(
Expand Down Expand Up @@ -1013,6 +1097,7 @@ fn should_get_updated_default_account_after_modification() {
AccountUpdate {
name: Some("Default Account".to_string()),
},
crate::storage::TEST_NOW,
)
.unwrap();

Expand Down Expand Up @@ -1076,15 +1161,26 @@ fn should_fall_back_to_the_tracked_default_when_the_reservation_is_stale() {
let anchor_number = anchor.anchor_number();
let origin = "https://example.com".to_string();
storage_borrow_mut(|storage| storage.write(anchor)).unwrap();
create_account_for_origin(anchor_number, origin.clone(), "Alice".to_string()).unwrap();
create_account_for_origin(
anchor_number,
origin.clone(),
"Alice".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();

// A number this identity does not hold. The write does not store it: the default is
// related to the account reference list and moved to a reference that is there, which
// is the tracked default here. So this asserts the repair rather than a tolerance —
// the read below answers from a default that exists, because no other kind was left
// behind.
storage_borrow_mut(|storage| {
storage.set_default_account(anchor_number, origin.clone(), Some(9_999))
storage.set_default_account(
anchor_number,
origin.clone(),
Some(9_999),
crate::storage::TEST_NOW,
)
})
.unwrap();

Expand Down Expand Up @@ -1116,8 +1212,20 @@ fn should_get_default_account_for_different_origins() {
let anchor_number = anchor.anchor_number();

// Create accounts for both origins
create_account_for_origin(anchor_number, origin1.clone(), "Alice".to_string()).unwrap();
create_account_for_origin(anchor_number, origin2.clone(), "Bob".to_string()).unwrap();
create_account_for_origin(
anchor_number,
origin1.clone(),
"Alice".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();
create_account_for_origin(
anchor_number,
origin2.clone(),
"Bob".to_string(),
crate::storage::TEST_NOW,
)
.unwrap();

// Run code under test
let result1 = get_default_account_for_origin(anchor_number, origin1.clone());
Expand Down
18 changes: 14 additions & 4 deletions src/internet_identity/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,13 @@ fn create_account(
match check_authorization(anchor_number) {
Ok(_) => {
// check if this anchor and acc are actually linked
account_management::create_account_for_origin(anchor_number, origin, name)
.map(|acc| acc.to_info())
account_management::create_account_for_origin(
anchor_number,
origin,
name,
ic_cdk::api::time(),
)
.map(|acc| acc.to_info())
}
Err(err) => Err(CreateAccountError::Unauthorized(err.principal)),
}
Expand All @@ -439,6 +444,7 @@ fn update_account(
account_number,
origin,
update,
ic_cdk::api::time(),
)
.map(|acc| acc.to_info()),
Err(err) => Err(UpdateAccountError::Unauthorized(err.principal)),
Expand Down Expand Up @@ -481,8 +487,12 @@ fn set_default_account(
) -> Result<AccountInfo, SetDefaultAccountError> {
check_authz_and_record_activity(anchor_number).map_err(SetDefaultAccountError::from)?;

let result =
account_management::set_default_account_for_origin(anchor_number, origin, account_number)?;
let result = account_management::set_default_account_for_origin(
anchor_number,
origin,
account_number,
ic_cdk::api::time(),
)?;
anchor_management::post_operation_bookkeeping(anchor_number, Operation::SetDefaultAccount);
Ok(result)
}
Expand Down
Loading
Loading