296 lines
14 KiB
TypeScript
296 lines
14 KiB
TypeScript
import { App, AppState } from "../app";
|
|
import { Server, ServerConfig } from "../server";
|
|
import { StubMessenger } from "../messenger";
|
|
import { MFAMessage, InviteCreatedMessage, MemberAddedMessage } from "../messages";
|
|
import { DirectSender } from "../transport";
|
|
import { MemoryStorage } from "../storage";
|
|
import { MemoryAttachmentStorage } from "../attachment";
|
|
import { ErrorCode } from "../error";
|
|
import { OrgType } from "../org";
|
|
import { Logger } from "../log";
|
|
import { MFAPurpose } from "../mfa";
|
|
import { Spec, assertResolve, assertReject } from "./spec";
|
|
|
|
export function appSpec(): Spec {
|
|
console.log("testing app");
|
|
|
|
const clientUrl = "https://padloc.app";
|
|
const messenger = new StubMessenger();
|
|
const server = new Server(
|
|
new ServerConfig({ clientUrl, reportErrors: "support@padloc.app" }),
|
|
new MemoryStorage(),
|
|
messenger,
|
|
new Logger(new MemoryStorage()),
|
|
new MemoryAttachmentStorage()
|
|
);
|
|
const app = new App(new DirectSender(server));
|
|
const otherApp = new App(new DirectSender(server));
|
|
|
|
const user = {
|
|
email: "lengden@olga.com",
|
|
name: "Lengden Olga",
|
|
password: "correct battery horse staple",
|
|
};
|
|
const otherUser = {
|
|
email: "max@mustermann.com",
|
|
name: "Max Mustermann",
|
|
password: "password",
|
|
};
|
|
let sharedVaultID = "";
|
|
// let otherVaultID = "";
|
|
|
|
return (test, assert) => {
|
|
test("App initializes successfully", async () => {
|
|
await app.loaded;
|
|
});
|
|
|
|
test("Signup", async () => {
|
|
await app.requestMFACode(user.email, MFAPurpose.Signup);
|
|
const message = messenger.lastMessage(user.email);
|
|
|
|
assert.instanceOf(message, MFAMessage);
|
|
|
|
const code = (message! as MFAMessage).request.code;
|
|
|
|
const { token } = await app.retrieveMFAToken(user.email, code, MFAPurpose.Signup);
|
|
|
|
await app.signup({ ...user, verify: token });
|
|
|
|
assert.isFalse(app.state.locked, "App should be in unlocked state after signup.");
|
|
assert.isNotNull(app.account, "Account object should be populated after signup.");
|
|
|
|
const account = app.account!;
|
|
assert.ownInclude(account, { email: user.email, name: user.name }, "Account info should be set correctly.");
|
|
assert.isNotNull(app.mainVault, "Main vault should be created.");
|
|
});
|
|
|
|
test("Create Personal Vault Item", async () => {
|
|
const item = await app.createItem("My First Item", app.mainVault!);
|
|
assert.equal(app.mainVault!.items.size, 1, "Item count should be 1.");
|
|
assert.ok(app.getItem(item.id), "Item should be accessible by ID.");
|
|
assert.equal(app.getItem(item.id)!.item, item);
|
|
assert.equal(app.getItem(item.id)!.vault, app.mainVault);
|
|
});
|
|
|
|
test("Create Org", async () => {
|
|
const org = await app.createOrg("My Org", OrgType.Business);
|
|
assert.equal(org.name, "My Org", "Organization name should be correct.");
|
|
assert.ok(org.id, "Organization ID should be set.");
|
|
assert.isTrue(org.isOwner(app.account!), "Account should be organization owner.");
|
|
assert.equal(app.state.orgs.length, 1);
|
|
await assertResolve(
|
|
assert,
|
|
() => app.account!.verifyOrg(app.state.orgs[0]),
|
|
"Organization should be verified successfully."
|
|
);
|
|
});
|
|
|
|
test("Create Vault", async () => {
|
|
const name = "My Shared Vault";
|
|
const vault = await app.createVault(name, app.state.orgs[0], [{ id: app.account!.id, readonly: false }]);
|
|
assert.equal(vault.name, name);
|
|
await app.synchronize();
|
|
assert.equal(app.state.vaults.length, 2);
|
|
});
|
|
|
|
test("Invite Member", async () => {
|
|
let org = app.state.orgs[0];
|
|
let [invite] = await app.createInvites(org, [otherUser.email]);
|
|
// Remember secret - in practice this will be communicated
|
|
// directly between the invitor and invitee
|
|
const { secret } = invite;
|
|
assert.equal(invite.email, otherUser.email);
|
|
const inviteMessage = messenger.lastMessage(otherUser.email) as InviteCreatedMessage;
|
|
assert.instanceOf(inviteMessage, InviteCreatedMessage);
|
|
const linkPattern = new RegExp(`${clientUrl}/invite/${org.id}/${invite.id}\\?email=(.*)&verify=(.*)`);
|
|
assert.match(inviteMessage.link, linkPattern);
|
|
const [, email, verify] = inviteMessage.link.match(linkPattern)!;
|
|
|
|
assert.equal(email, invite.email);
|
|
|
|
await otherApp.signup({ ...otherUser, verify });
|
|
invite = (await otherApp.getInvite(org.id, invite.id))!;
|
|
assert.isTrue(await otherApp.acceptInvite(invite, secret));
|
|
invite = (await app.getInvite(org.id, invite.id))!;
|
|
invite.secret = secret;
|
|
assert.isTrue(await invite.verifyInvitee());
|
|
await app.confirmInvite(invite);
|
|
assert.isTrue(app.state.orgs[0].isMember(otherApp.account!));
|
|
await otherApp.synchronize();
|
|
assert.equal(otherApp.state.orgs.length, 1);
|
|
assert.isTrue(otherApp.state.orgs[0].isMember(otherApp.account!));
|
|
|
|
const addedMessage = messenger.lastMessage(otherUser.email) as MemberAddedMessage;
|
|
assert.instanceOf(addedMessage, MemberAddedMessage);
|
|
assert.equal(addedMessage.org.id, org.id);
|
|
});
|
|
|
|
test("Create Group", async () => {
|
|
const org = app.state.orgs[0];
|
|
await app.createGroup(org, "Everyone", org.members)!;
|
|
const group = app.state.orgs[0].getGroup("Everyone")!;
|
|
assert.ok(group);
|
|
const vault = await app.createVault(
|
|
"Another Vault",
|
|
app.state.orgs[0],
|
|
[],
|
|
[{ name: group.name, readonly: false }]
|
|
);
|
|
sharedVaultID = vault.id;
|
|
await otherApp.synchronize();
|
|
assert.equal(otherApp.vaults.length, 2);
|
|
});
|
|
|
|
// test("Make Admin", async () => {
|
|
// const vault = app.getVault(sharedVaultID)!;
|
|
// const member = vault.members.get(otherApp.account!.id)!;
|
|
// vault.members.update({ ...member, permissions: { ...member.permissions, manage: true } });
|
|
// await app.syncVault(vault);
|
|
// await otherApp.syncVault(vault);
|
|
// assert.isTrue(app.getVault(sharedVaultID)!.isAdmin(otherApp.account));
|
|
// assert.isTrue(otherApp.getVault(sharedVaultID)!.isAdmin(otherApp.account));
|
|
// // @ts-ignore
|
|
// assert.isOk(otherApp.getVault(sharedVaultID)!._privateKey);
|
|
// await otherApp.syncVault(vault);
|
|
// await app.syncVault(vault);
|
|
// });
|
|
//
|
|
// test("Remove Admin", async () => {
|
|
// const vault = app.getVault(sharedVaultID)!;
|
|
// const member = vault.members.get(otherApp.account!.id)!;
|
|
// vault.members.update({ ...member, permissions: { ...member.permissions, manage: false } });
|
|
// await app.reinitializeVault(vault);
|
|
// await otherApp.synchronize();
|
|
//
|
|
// assert.isFalse(
|
|
// otherApp.getVault(sharedVaultID)!.isAdmin(otherApp.account),
|
|
// "Other member should no longer be admin"
|
|
// );
|
|
//
|
|
// assert.isTrue(
|
|
// otherApp.getVault(sharedVaultID)!.getMember(otherApp.account!)!.suspended,
|
|
// "Other member should be suspended"
|
|
// );
|
|
//
|
|
// const message = messenger.lastMessage(otherApp.account!.email);
|
|
// assert.instanceOf(message, InviteCreatedMessage);
|
|
// const { id: inviteID } = (message as InviteCreatedMessage).invite;
|
|
// const { secret } = vault.invites.get(inviteID)!;
|
|
// let invite = (await otherApp.getInvite(vault.id, inviteID))!;
|
|
// await otherApp.acceptInvite(invite, secret);
|
|
// invite = (await app.getInvite(vault.id, invite.id))!;
|
|
// assert.isTrue(await invite.verify());
|
|
// await app.confirmInvite(invite);
|
|
// await otherApp.syncVault(vault);
|
|
// assert.isFalse(otherApp.getVault(sharedVaultID)!.isAdmin());
|
|
// assert.isFalse(otherApp.getVault(sharedVaultID)!.isSuspended());
|
|
// assert.isTrue(otherApp.getVault(sharedVaultID)!.hasItemsAccess());
|
|
// assert.equal(app.items.length, 1);
|
|
// });
|
|
//
|
|
test("Simulataneous Edit", async () => {
|
|
const [item1, item2] = await Promise.all([
|
|
app.createItem("Added Item 1", { id: sharedVaultID }),
|
|
otherApp.createItem("Added Item 2", { id: sharedVaultID }),
|
|
]);
|
|
|
|
await Promise.all([app.syncVault({ id: sharedVaultID }), otherApp.syncVault({ id: sharedVaultID })]);
|
|
await Promise.all([app.syncVault({ id: sharedVaultID }), otherApp.syncVault({ id: sharedVaultID })]);
|
|
|
|
assert.ok(app.getItem(item2.id), "Created item from second app should should show up in first app");
|
|
assert.ok(otherApp.getItem(item1.id), "Created item from first app should should show up in second app");
|
|
|
|
await app.updateItem(item1, { name: "Edited Item" });
|
|
const item3 = await app.createItem("Added Item 3", app.getVault(sharedVaultID)!);
|
|
await otherApp.deleteItems([item2]);
|
|
|
|
await Promise.all([app.syncVault({ id: sharedVaultID }), otherApp.syncVault({ id: sharedVaultID })]);
|
|
await Promise.all([app.syncVault({ id: sharedVaultID }), otherApp.syncVault({ id: sharedVaultID })]);
|
|
|
|
assert.isNull(app.getItem(item2.id));
|
|
assert.ok(otherApp.getItem(item3.id), "Created Item show up other instance");
|
|
assert.equal(otherApp.getItem(item1.id)!.item.name, "Edited Item");
|
|
});
|
|
//
|
|
// test("Archive Vault", async () => {
|
|
// let vault = await app.createVault("Test");
|
|
// otherVaultID = vault.id;
|
|
// // const invite = await app.createInvite(vault, otherApp.account!.email);
|
|
// // await otherApp.acceptInvite(invite, invite.secret);
|
|
// // await app.confirmInvite(invite);
|
|
// // await app.syncVault(vault);
|
|
// assert.isTrue(vault.isMember(app.account!));
|
|
// await app.archiveVault(vault);
|
|
// vault = app.getVault(vault.id)!;
|
|
// assert.isTrue(vault.archived);
|
|
// });
|
|
//
|
|
// test("Unarchive Vault", async () => {
|
|
// await app.unarchiveVault(app.getVault(otherVaultID)!);
|
|
// assert.isFalse(app.getVault(otherVaultID)!.archived);
|
|
// });
|
|
//
|
|
// test("Delete Vault", async () => {
|
|
// await app.deleteVault(app.getVault(otherVaultID)!);
|
|
// assert.isNull(app.getVault(otherVaultID));
|
|
// });
|
|
//
|
|
// test("Remove Member", async () => {
|
|
// await app.removeMember(app.state.vaults[1], app.state.vaults[1].members.get(otherApp.account!.id)!);
|
|
// await otherApp.synchronize();
|
|
// assert.isNull(app.state.vaults[1].members.get(otherApp.account!.id));
|
|
// assert.isNull(app.state.vaults[2].members.get(otherApp.account!.id));
|
|
// assert.equal(otherApp.vaults.length, 1);
|
|
// assert.isNull(otherApp.getVault(app.state.vaults[1].id));
|
|
// assert.isNull(otherApp.getVault(app.state.vaults[2].id));
|
|
// });
|
|
//
|
|
test("Lock", async () => {
|
|
await app.lock();
|
|
assert.isTrue(app.state.locked, "App should be in 'locked' state.");
|
|
assert.isNotOk(app.account!.privateKey, "Private key should be inaccessible after locking.");
|
|
assert.equal(app.mainVault!.items.size, 0, "Main vault should be inacessible after locking.");
|
|
});
|
|
|
|
test("Unlock", async () => {
|
|
await app.unlock(user.password);
|
|
assert.isFalse(app.state.locked, "App should be in 'unlocked' state.");
|
|
assert.instanceOf(app.account!.privateKey, Uint8Array, "Private key should be loaded.");
|
|
assert.isNotNull(app.mainVault, "Main vault should be loaded.");
|
|
assert.equal(app.mainVault!.items.size, 1, "Items should be loaded.");
|
|
});
|
|
|
|
test("Logout", async () => {
|
|
await app.logout();
|
|
|
|
const state = await app.storage.get(AppState, app.state.id);
|
|
assert.isNotOk(state.account, "Account should be unloaded.");
|
|
assert.isNotOk(state.session, "Session should be unloaded.");
|
|
assert.equal(state.orgs.length, 0, "Orgs should be unloaded.");
|
|
assert.equal(state.vaults.length, 0, "Vaults should be unloaded.");
|
|
});
|
|
|
|
test("Login", async () => {
|
|
const app = new App(new DirectSender(server));
|
|
await assertReject(
|
|
assert,
|
|
() => app.login(user.email, user.password),
|
|
ErrorCode.MFA_REQUIRED,
|
|
"Logging in from a new device should require email verification."
|
|
);
|
|
|
|
await app.requestMFACode(user.email, MFAPurpose.Login);
|
|
const message = messenger.lastMessage(user.email);
|
|
const code = (message! as MFAMessage).request.code;
|
|
const { token } = await app.retrieveMFAToken(user.email, code, MFAPurpose.Login);
|
|
|
|
await app.login(user.email, user.password, token);
|
|
|
|
assert.isNotNull(app.account, "Account should be loaded.");
|
|
const account = app.account!;
|
|
assert.ownInclude(account, { email: user.email, name: user.name }, "Account info should be correct.");
|
|
assert.equal(app.mainVault!.items.size, 1, "Vault Items should be loaded");
|
|
});
|
|
};
|
|
}
|