Skip to content

Commit 36522af

Browse files
authored
Unified Identity - Account Linking (#55)
1 parent dffd5ac commit 36522af

File tree

13 files changed

+407
-330
lines changed

13 files changed

+407
-330
lines changed

Thirdweb.Console/Program.cs

Lines changed: 75 additions & 200 deletions
Large diffs are not rendered by default.

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.Types.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,39 @@ internal VerifyResult(bool isNewUser, string authToken, string walletUserId, str
2424
internal string PhoneNumber { get; }
2525
}
2626

27+
[DataContract]
28+
internal class AccountConnectResponse
29+
{
30+
[DataMember(Name = "linkedAccounts", IsRequired = true)]
31+
public List<LinkedAccount> LinkedAccounts { get; set; }
32+
}
33+
34+
[DataContract]
35+
public class LinkedAccount
36+
{
37+
[DataMember(Name = "type", IsRequired = true)]
38+
public string Type { get; set; }
39+
40+
[DataMember(Name = "details", IsRequired = true)]
41+
public LinkedAccountDetails Details { get; set; }
42+
43+
[DataContract]
44+
public class LinkedAccountDetails
45+
{
46+
[DataMember(Name = "email", EmitDefaultValue = false)]
47+
public string Email { get; set; }
48+
49+
[DataMember(Name = "address", EmitDefaultValue = false)]
50+
public string Address { get; set; }
51+
52+
[DataMember(Name = "phone", EmitDefaultValue = false)]
53+
public string Phone { get; set; }
54+
55+
[DataMember(Name = "id", EmitDefaultValue = false)]
56+
public string Id { get; set; }
57+
}
58+
}
59+
2760
[DataContract]
2861
private class SendEmailOtpReturnType
2962
{

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ namespace Thirdweb.EWS
55
{
66
internal abstract class ServerBase
77
{
8-
internal abstract Task<string> VerifyThirdwebClientIdAsync(string domain);
8+
internal abstract Task<List<Server.LinkedAccount>> LinkAccountAsync(string currentAccountToken, string authTokenToConnect);
9+
internal abstract Task<List<Server.LinkedAccount>> GetLinkedAccountsAsync(string currentAccountToken);
10+
911
internal abstract Task<Server.UserWallet> FetchUserDetailsAsync(string emailAddress, string authToken);
1012
internal abstract Task StoreAddressAndSharesAsync(string walletAddress, string authShare, string encryptedRecoveryShare, string authToken);
1113

@@ -50,16 +52,29 @@ internal Server(ThirdwebClient client, IThirdwebHttpClient httpClient)
5052
thirdwebHttpClientType = httpClient.GetType();
5153
}
5254

53-
// embedded-wallet/verify-thirdweb-client-id
54-
internal override async Task<string> VerifyThirdwebClientIdAsync(string parentDomain)
55+
// account/connect
56+
internal override async Task<List<LinkedAccount>> LinkAccountAsync(string currentAccountToken, string authTokenToConnect)
5557
{
56-
Dictionary<string, string> queryParams = new() { { "clientId", clientId }, { "parentDomain", parentDomain } };
57-
var uri = MakeUri2023("/embedded-wallet/verify-thirdweb-client-id", queryParams);
58-
var content = MakeHttpContent(new { clientId, parentDomain });
58+
var uri = MakeUri2024("/account/connect");
59+
var content = MakeHttpContent(new { accountAuthTokenToConnect = authTokenToConnect });
60+
httpClient.AddHeader("Authorization", $"Bearer iaw-auth-token:{currentAccountToken}");
5961
var response = await httpClient.PostAsync(uri.ToString(), content).ConfigureAwait(false);
62+
httpClient.RemoveHeader("Authorization");
6063
await CheckStatusCodeAsync(response).ConfigureAwait(false);
61-
var error = await DeserializeAsync<HttpErrorWithMessage>(response).ConfigureAwait(false);
62-
return error.Error;
64+
65+
var res = await DeserializeAsync<AccountConnectResponse>(response).ConfigureAwait(false);
66+
return res == null || res.LinkedAccounts == null || res.LinkedAccounts.Count == 0 ? throw new InvalidOperationException("No linked accounts returned") : res.LinkedAccounts;
67+
}
68+
69+
// accounts GET
70+
internal override async Task<List<LinkedAccount>> GetLinkedAccountsAsync(string currentAccountToken)
71+
{
72+
var uri = MakeUri2024("/accounts");
73+
var response = await SendHttpWithAuthAsync(uri, currentAccountToken).ConfigureAwait(false);
74+
await CheckStatusCodeAsync(response).ConfigureAwait(false);
75+
76+
var res = await DeserializeAsync<AccountConnectResponse>(response).ConfigureAwait(false);
77+
return res == null || res.LinkedAccounts == null || res.LinkedAccounts.Count == 0 ? new List<LinkedAccount>() : res.LinkedAccounts;
6378
}
6479

6580
// embedded-wallet/embedded-wallet-user-details

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Storage/LocalStorage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal LocalStorage(string clientId, string storageDirectoryPath = null)
2020
{
2121
string directory;
2222
directory = storageDirectoryPath ?? Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
23-
directory = Path.Combine(directory, "EWS");
23+
directory = Path.Combine(directory, "Thirdweb", "InAppWallet");
2424
_ = Directory.CreateDirectory(directory);
2525
filePath = Path.Combine(directory, $"{clientId}.txt");
2626
try
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Thirdweb.EWS
2+
{
3+
internal partial class EmbeddedWallet
4+
{
5+
public async Task<List<Server.LinkedAccount>> LinkAccountAsync(string currentAccountToken, string authTokenToConnect)
6+
{
7+
return await server.LinkAccountAsync(currentAccountToken, authTokenToConnect).ConfigureAwait(false);
8+
}
9+
10+
public async Task<List<Server.LinkedAccount>> GetLinkedAccountsAsync(string currentAccountToken)
11+
{
12+
return await server.GetLinkedAccountsAsync(currentAccountToken).ConfigureAwait(false);
13+
}
14+
}
15+
}

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.AuthEndpoint.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ namespace Thirdweb.EWS
22
{
33
internal partial class EmbeddedWallet
44
{
5-
public async Task<VerifyResult> SignInWithAuthEndpointAsync(string payload, string encryptionKey)
5+
public async Task<Server.VerifyResult> SignInWithAuthEndpointAsync(string payload)
66
{
7-
var result = await server.VerifyAuthEndpointAsync(payload).ConfigureAwait(false);
8-
return await PostAuthSetup(result, encryptionKey, "AuthEndpoint").ConfigureAwait(false);
7+
return await server.VerifyAuthEndpointAsync(payload).ConfigureAwait(false);
98
}
109
}
1110
}

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.EmailOTP.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,10 @@ internal partial class EmbeddedWallet
1111
return (userWallet.IsNewUser, isNewDevice);
1212
}
1313

14-
public async Task<VerifyResult> VerifyEmailOtpAsync(string emailAddress, string otp)
14+
public async Task<Server.VerifyResult> VerifyEmailOtpAsync(string emailAddress, string otp)
1515
{
1616
emailAddress = emailAddress.ToLower();
17-
try
18-
{
19-
var result = await server.VerifyEmailOtpAsync(emailAddress, otp).ConfigureAwait(false);
20-
return await PostAuthSetup(result, null, "Email").ConfigureAwait(false);
21-
}
22-
catch (VerificationException ex)
23-
{
24-
return new VerifyResult(ex.CanRetry);
25-
}
17+
return await server.VerifyEmailOtpAsync(emailAddress, otp).ConfigureAwait(false);
2618
}
2719
}
2820
}

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.JWT.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ namespace Thirdweb.EWS
22
{
33
internal partial class EmbeddedWallet
44
{
5-
public async Task<VerifyResult> SignInWithJwtAsync(string jwt, string encryptionKey)
5+
public async Task<Server.VerifyResult> SignInWithJwtAsync(string jwt)
66
{
7-
var result = await server.VerifyJwtAsync(jwt).ConfigureAwait(false);
8-
return await PostAuthSetup(result, encryptionKey, "JWT").ConfigureAwait(false);
7+
return await server.VerifyJwtAsync(jwt).ConfigureAwait(false);
98
}
109
}
1110
}

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@ namespace Thirdweb.EWS
44
{
55
internal partial class EmbeddedWallet
66
{
7-
public async Task VerifyThirdwebClientIdAsync(string domain)
7+
internal string GetCurrentAuthToken()
88
{
9-
var error = await server.VerifyThirdwebClientIdAsync(domain).ConfigureAwait(false);
10-
if (error != "")
11-
{
12-
throw new InvalidOperationException($"Invalid thirdweb client id for domain {domain} | {error}");
13-
}
9+
return localStorage.Data?.AuthToken;
1410
}
1511

16-
private async Task<VerifyResult> PostAuthSetup(Server.VerifyResult result, string twManagedRecoveryCodeOverride, string authProvider)
12+
internal async Task<VerifyResult> PostAuthSetup(Server.VerifyResult result, string twManagedRecoveryCodeOverride, string authProvider)
1713
{
1814
var walletUserId = result.WalletUserId;
1915
var authToken = result.AuthToken;
@@ -52,15 +48,12 @@ public async Task<User> GetUserAsync(string email, string phone, string authProv
5248
switch (userWallet.Status)
5349
{
5450
case "Logged Out":
55-
await SignOutAsync().ConfigureAwait(false);
5651
throw new InvalidOperationException("User is logged out");
5752
case "Logged In, Wallet Uninitialized":
58-
await SignOutAsync().ConfigureAwait(false);
5953
throw new InvalidOperationException("User is logged in but wallet is uninitialized");
6054
case "Logged In, Wallet Initialized":
6155
if (string.IsNullOrEmpty(localStorage.Data?.DeviceShare))
6256
{
63-
await SignOutAsync().ConfigureAwait(false);
6457
throw new InvalidOperationException("User is logged in but wallet is uninitialized");
6558
}
6659

@@ -70,12 +63,10 @@ public async Task<User> GetUserAsync(string email, string phone, string authProv
7063

7164
if ((email != null && email != emailAddress) || (phone != null && phone != phoneNumber))
7265
{
73-
await SignOutAsync().ConfigureAwait(false);
7466
throw new InvalidOperationException("User email or phone number do not match");
7567
}
7668
else if (email == null && localStorage.Data.AuthProvider != authProvider)
7769
{
78-
await SignOutAsync().ConfigureAwait(false);
7970
throw new InvalidOperationException($"User auth provider does not match. Expected {localStorage.Data.AuthProvider}, got {authProvider}");
8071
}
8172
else if (authShare == null)

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.OAuth.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ namespace Thirdweb.EWS
44
{
55
internal partial class EmbeddedWallet
66
{
7-
public async Task<VerifyResult> SignInWithOauthAsync(string authProvider, string authResult)
7+
public async Task<Server.VerifyResult> SignInWithOauthAsync(string authProvider, string authResult)
88
{
9-
var result = await server.VerifyOAuthAsync(authResult).ConfigureAwait(false);
10-
return await PostAuthSetup(result, null, authProvider).ConfigureAwait(false);
9+
return await server.VerifyOAuthAsync(authResult).ConfigureAwait(false);
1110
}
1211

1312
public async Task<string> FetchHeadlessOauthLoginLinkAsync(string authProvider, string platform)

0 commit comments

Comments
 (0)