Skip to content

Commit

Permalink
Create alias contracts in frontend in create-daml-app (digital-asset#…
Browse files Browse the repository at this point in the history
…12848)

* Create alias contracts in frontend in create-daml-app

Creating them in the setup script is nice but it makes it much harder
to port this to Daml hub where we cannot rely on this.

This also requires figuring out the public party in the frontend.

I’ve chosen to infer it from the user rights which seems broadly
sensible.

For the backwards compat mode, I just hardcoded it because there isn’t
a great way to figure it out.

changelog_begin
changelog_end

* .

changelog_begin
changelog_end
  • Loading branch information
cocreature authored Feb 10, 2022
1 parent 83ed13d commit 5b4fdf0
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 30 deletions.
47 changes: 25 additions & 22 deletions templates/create-daml-app-test-resources/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ let publicParty: string | undefined;

const adminLedger = new Ledger({token: authConfig.makeToken("participant_admin")});

const toAlias = (userId: string): string =>
userId.charAt(0).toUpperCase() + userId.slice(1);

// Function to generate unique party names for us.
let nextPartyId = 1;
const getParty = async () : [string, string] => {
Expand Down Expand Up @@ -260,6 +263,14 @@ test('log in as three different users and start following each other', async ()
const page1 = await newUiPage();
await login(page1, user1);

// Log in as Party 2.
const page2 = await newUiPage();
await login(page2, user2);

// Log in as Party 3.
const page3 = await newUiPage();
await login(page3, user3);

// Party 1 should initially follow no one.
const noFollowing1 = await page1.$$('.test-select-following');
expect(noFollowing1).toEqual([]);
Expand All @@ -270,19 +281,15 @@ test('log in as three different users and start following each other', async ()
await follow(page1, party2);
await waitForFollowers(page1, 1);
const followingList1 = await page1.$$eval('.test-select-following', following => following.map(e => e.innerHTML));
expect(followingList1).toEqual([party2]);
expect(followingList1).toEqual([toAlias(user2)]);

// Add Party 3 as well and check both are in the list.
await follow(page1, party3);
await waitForFollowers(page1, 2);
const followingList11 = await page1.$$eval('.test-select-following', following => following.map(e => e.innerHTML));
expect(followingList11).toHaveLength(2);
expect(followingList11).toContain(party2);
expect(followingList11).toContain(party3);

// Log in as Party 2.
const page2 = await newUiPage();
await login(page2, user2);
// Add Party 3 as well and check both are in the list.
await follow(page1, party3);
await waitForFollowers(page1, 2);
const followingList11 = await page1.$$eval('.test-select-following', following => following.map(e => e.innerHTML));
expect(followingList11).toHaveLength(2);
expect(followingList11).toContain(toAlias(user2));
expect(followingList11).toContain(toAlias(user3));

// Party 2 should initially follow no one.
const noFollowing2 = await page2.$$('.test-select-following');
Expand All @@ -291,7 +298,7 @@ test('log in as three different users and start following each other', async ()
// However, Party 2 should see Party 1 in the network.
await page2.waitForSelector('.test-select-user-in-network');
const network2 = await page2.$$eval('.test-select-user-in-network', users => users.map(e => e.innerHTML));
expect(network2).toEqual([party1]);
expect(network2).toEqual([toAlias(user1)]);

// Follow Party 1 using the 'add user' icon on the right.
await page2.waitForSelector('.test-select-add-user-icon');
Expand All @@ -310,18 +317,14 @@ test('log in as three different users and start following each other', async ()
await waitForFollowers(page2, 2);
const followingList2 = await page2.$$eval('.test-select-following', following => following.map(e => e.innerHTML));
expect(followingList2).toHaveLength(2);
expect(followingList2).toContain(party1);
expect(followingList2).toContain(party3);
expect(followingList2).toContain(toAlias(user1));
expect(followingList2).toContain(toAlias(user3));

// Party 1 should now also see Party 2 in the network (but not Party 3 as they
// didn't yet started following Party 1).
await page1.waitForSelector('.test-select-user-in-network');
const network1 = await page1.$$eval('.test-select-user-in-network', following => following.map(e => e.innerHTML));
expect(network1).toEqual([party2]);

// Log in as Party 3.
const page3 = await newUiPage();
await login(page3, user3);
expect(network1).toEqual([toAlias(user2)]);

// Party 3 should follow no one.
const noFollowing3 = await page3.$$('.test-select-following');
Expand All @@ -331,8 +334,8 @@ test('log in as three different users and start following each other', async ()
await page3.waitForSelector('.test-select-user-in-network');
const network3 = await page3.$$eval('.test-select-user-in-network', following => following.map(e => e.innerHTML));
expect(network3).toHaveLength(2);
expect(network3).toContain(party1);
expect(network3).toContain(party2);
expect(network3).toContain(toAlias(user1));
expect(network3).toContain(toAlias(user2));

await page1.close();
await page2.close();
Expand Down
5 changes: 1 addition & 4 deletions templates/create-daml-app/daml/Setup.daml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import DA.Optional (fromSomeNote)
import qualified DA.Text as T
import Daml.Script

import User (Alias(..))

-- | A test user for the create-daml-app network.
data TestUser = TestUser with
alias : Text
Expand All @@ -24,7 +22,6 @@ createTestUser : TestUser -> Script Party
createTestUser TestUser{alias, public} = do
u <- getOrCreateUser alias (Some public)
let p = getPrimaryParty u
submit p $ createCmd $ Alias p alias public
pure p

-- | Create the public party.
Expand All @@ -44,7 +41,7 @@ getOrCreateUser alias publicM = do
UserNotFound _ -> do
p <- allocateParty alias
let u = User userId (Some p)
createUser u $ [CanActAs p] ++ [CanReadAs public | Some public <- [publicM]]
createUser u $ CanActAs p :: [CanReadAs public | Some public <- [publicM]]
pure u

-- | Convert a text to a valid user id.
Expand Down
1 change: 0 additions & 1 deletion templates/create-daml-app/daml/User.daml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module User where
-- MAIN_TEMPLATE_BEGIN
template User with
username: Party
public: Party
following: [Party]
where
signatory username
Expand Down
1 change: 1 addition & 0 deletions templates/create-daml-app/ui/src/Credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { User } from "@daml/ledger";

export type Credentials = {
party: string;
publicParty: string;
token: string;
user: User;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type Props = {
onLogin: (credentials: Credentials) => void;
}

const toAlias = (userId: string): string =>
userId.charAt(0).toUpperCase() + userId.slice(1);

/**
* React component for the login screen of the `App`.
*/
Expand All @@ -24,9 +27,13 @@ const LoginScreen: React.FC<Props> = ({onLogin}) => {
const ledger = new Ledger({token: credentials.token});
let userContract = await ledger.fetchByKey(User.User, credentials.party);
if (userContract === null) {
const user = {username: credentials.party, following: [], public: 'public'};
const user = {username: credentials.party, following: []};
userContract = await ledger.create(User.User, user);
}
let userAlias = await ledger.fetchByKey(User.Alias, {_1: credentials.party, _2: credentials.publicParty});
if (userAlias === null) {
await ledger.create(User.Alias, {username: credentials.party, alias: toAlias(credentials.user.userId), public: credentials.publicParty});
}
onLogin(credentials);
} catch(error) {
alert(`Unknown error:\n${JSON.stringify(error)}`);
Expand Down Expand Up @@ -72,8 +79,14 @@ const LoginScreen: React.FC<Props> = ({onLogin}) => {
alert(`Failed to login as '${username}':\n${errorMsg}`);
throw error;
});
const publicParty:string = await auth.userManagement.publicParty(username, ledger).catch((error) => {
const errorMsg = error instanceof Error ? error.toString() : JSON.stringify(error);
alert(`Failed to login as '${username}':\n${errorMsg}`);
throw error;
});
await login({user: {userId: username, primaryParty: primaryParty},
party: primaryParty,
publicParty: publicParty,
token: auth.makeToken(username)});
}

Expand All @@ -100,7 +113,8 @@ const LoginScreen: React.FC<Props> = ({onLogin}) => {
<DamlHubLoginBtn
onLogin={creds => {
if (creds) {
login({party:creds.party, user: {userId: creds.partyName, primaryParty: creds.party}, token:creds.token});
// TODO (MK) Fix public party in Daml hub
login({party:creds.party, publicParty: "FIXME", user: {userId: creds.partyName, primaryParty: creds.party}, token:creds.token});
}
}}
options={{
Expand All @@ -120,9 +134,11 @@ const LoginScreen: React.FC<Props> = ({onLogin}) => {
if (isLoading === false && isAuthenticated === true) {
if (user !== undefined) {
const party = user["https://daml.com/ledger-api"];
// TODO (MK) Fix public party with Auth0
const creds: Credentials = {
user: {userId: user.email ?? user.name ?? party, primaryParty: party},
party: party,
publicParty: "FIXME",
token: (await getAccessTokenSilently({
audience: "https://daml.com/ledger-api"}))};
login(creds);
Expand Down
17 changes: 16 additions & 1 deletion templates/create-daml-app/ui/src/config.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

import { encode } from 'jwt-simple';
import { isRunningOnHub } from '@daml/hub-react';
import Ledger from '@daml/ledger';
import Ledger, { CanReadAs } from '@daml/ledger';

export type UserManagement = {
tokenPayload: (loginName: string, ledgerId: string) => Object,
primaryParty: (loginName: string, ledger: Ledger) => Promise<string>,
publicParty: (loginName: string, ledger: Ledger) => Promise<string>,
};

export type Insecure = {
Expand Down Expand Up @@ -39,6 +40,9 @@ export const noUserManagement: UserManagement = {
}
}),
primaryParty: async (loginName: string, ledger: Ledger) => loginName,
// Without user management, we force a specific party id here because
// we mainly care about this for vmbc and there we can support this.
publicParty: async (loginName: string, ledger: Ledger) => 'public',
};

// Used on SDK >= 2.0.0 with the exception of VMBC
Expand All @@ -56,6 +60,17 @@ export const withUserManagement: UserManagement = {
throw new Error(`User '${loginName}' has no primary party`);
}

},
publicParty: async (loginName, ledger: Ledger) => {
const rights = await ledger.listUserRights();
const readAsRights: CanReadAs[] = rights.filter((x) : x is CanReadAs => x.type === "CanReadAs");
if (readAsRights.length === 0) {
throw new Error(`User '${loginName} has no readAs claims for a public party`);
} else if (readAsRights.length > 1) {
throw new Error(`User '${loginName} has readAs claims for more than one party`);
} else {
return readAsRights[0].party;
}
}
};

Expand Down

0 comments on commit 5b4fdf0

Please sign in to comment.