mirror of
https://github.com/streetwriters/notesnook-sync-server.git
synced 2026-11-03 06:37:05 +01:00
Committing or discarding the keys of a password change that failed midway trusted the client to have just logged in. An older session could discard keys for a password that had been set, leaving them unwrappable. Both now require the token's auth_time to be after the keys were staged, and return login_required otherwise. Refreshed tokens keep the auth_time of the original login. Discarding still waits until the password change can no longer be running.
97 lines
3.9 KiB
C#
97 lines
3.9 KiB
C#
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using Notesnook.API.Models;
|
|
using Notesnook.API.Services;
|
|
|
|
namespace Notesnook.API.Tests;
|
|
|
|
public class UserKeysServiceTests(TestDatabase db) : IClassFixture<TestDatabase>
|
|
{
|
|
private static readonly long Minute = (long)TimeSpan.FromMinutes(1).TotalMilliseconds;
|
|
private static long Now() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
private static EncryptedData Key(string cipher) => new() { IV = "iv", Cipher = cipher, Salt = "salt" };
|
|
|
|
private UserKeysService Service() => new(db.CreateScope().Repositories);
|
|
|
|
// a user whose password change staged new keys at stagedAt and then failed
|
|
private async Task<(string UserId, long Revision)> UserWithPendingKeysAsync(long stagedAt)
|
|
{
|
|
var userId = ObjectId.GenerateNewId().ToString();
|
|
await db.CreateScope().Repositories.UsersSettings.Collection.InsertOneAsync(new UserSettings
|
|
{
|
|
UserId = userId,
|
|
Salt = "",
|
|
DataEncryptionKey = Key("old"),
|
|
Revision = 1
|
|
});
|
|
Assert.Equal(KeyWriteResult.Success, await Service().StageKeysAsync(userId, new UserKeys { DataEncryptionKey = Key("new") }, 1, stagedAt));
|
|
return (userId, 2);
|
|
}
|
|
|
|
private async Task<UserSettings> SettingsAsync(string userId) =>
|
|
await db.CreateScope().Repositories.UsersSettings.Collection.Find((u) => u.UserId == userId).FirstAsync();
|
|
|
|
[Fact]
|
|
public async Task A_session_from_before_the_keys_were_staged_cant_commit_them()
|
|
{
|
|
var stagedAt = Now() - Minute;
|
|
var (userId, revision) = await UserWithPendingKeysAsync(stagedAt);
|
|
|
|
Assert.Equal(KeyWriteResult.LoginRequired, await Service().CommitPendingKeysAsync(userId, revision, stagedAt - Minute));
|
|
|
|
var settings = await SettingsAsync(userId);
|
|
Assert.Equal("old", settings.DataEncryptionKey!.Cipher);
|
|
Assert.NotNull(settings.PendingKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_login_after_the_keys_were_staged_commits_them()
|
|
{
|
|
var stagedAt = Now() - Minute;
|
|
var (userId, revision) = await UserWithPendingKeysAsync(stagedAt);
|
|
|
|
Assert.Equal(KeyWriteResult.Success, await Service().CommitPendingKeysAsync(userId, revision, stagedAt + 1000));
|
|
|
|
var settings = await SettingsAsync(userId);
|
|
Assert.Equal("new", settings.DataEncryptionKey!.Cipher);
|
|
Assert.Null(settings.PendingKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_session_from_before_the_keys_were_staged_cant_discard_them()
|
|
{
|
|
// long enough ago that the password change can't still be running
|
|
var stagedAt = Now() - 10 * Minute;
|
|
var (userId, revision) = await UserWithPendingKeysAsync(stagedAt);
|
|
|
|
Assert.Equal(KeyWriteResult.LoginRequired, await Service().DiscardPendingKeysAsync(userId, revision, stagedAt - Minute));
|
|
|
|
Assert.NotNull((await SettingsAsync(userId)).PendingKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_login_after_the_keys_were_staged_discards_them_once_the_password_change_has_timed_out()
|
|
{
|
|
var stagedAt = Now() - 10 * Minute;
|
|
var (userId, revision) = await UserWithPendingKeysAsync(stagedAt);
|
|
|
|
Assert.Equal(KeyWriteResult.Success, await Service().DiscardPendingKeysAsync(userId, revision, stagedAt + 1000));
|
|
|
|
var settings = await SettingsAsync(userId);
|
|
Assert.Equal("old", settings.DataEncryptionKey!.Cipher);
|
|
Assert.Null(settings.PendingKeys);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_login_after_the_keys_were_staged_cant_discard_them_while_the_password_change_may_still_be_running()
|
|
{
|
|
var stagedAt = Now() - Minute;
|
|
var (userId, revision) = await UserWithPendingKeysAsync(stagedAt);
|
|
|
|
Assert.Equal(KeyWriteResult.PasswordChangeInProgress, await Service().DiscardPendingKeysAsync(userId, revision, stagedAt + 1000));
|
|
|
|
Assert.NotNull((await SettingsAsync(userId)).PendingKeys);
|
|
}
|
|
}
|