Skip to content

Commit 59087bd

Browse files
authored
Migrate OTP Flow to v2 (#52)
1 parent 3f5b090 commit 59087bd

File tree

19 files changed

+222
-679
lines changed

19 files changed

+222
-679
lines changed

Thirdweb.Console/Program.cs

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@
2222
Console.WriteLine($"Contract read result: {readResult}");
2323

2424
// Create wallets (this is an advanced use case, typically one wallet is plenty)
25-
var privateKeyWallet = await PrivateKeyWallet.Create(client: client, privateKeyHex: privateKey);
26-
var walletAddress = await privateKeyWallet.GetAddress();
25+
// var privateKeyWallet = await PrivateKeyWallet.Create(client: client, privateKeyHex: privateKey);
26+
// var walletAddress = await privateKeyWallet.GetAddress();
2727

28-
var chainData = await Utils.FetchThirdwebChainDataAsync(client, 421614);
29-
Console.WriteLine($"Chain data: {JsonConvert.SerializeObject(chainData, Formatting.Indented)}");
28+
// var chainData = await Utils.FetchThirdwebChainDataAsync(client, 421614);
29+
// Console.WriteLine($"Chain data: {JsonConvert.SerializeObject(chainData, Formatting.Indented)}");
3030

31-
var inAppWalletOAuth = await InAppWallet.Create(client: client, authProvider: AuthProvider.Telegram);
32-
if (!await inAppWalletOAuth.IsConnected())
33-
{
34-
_ = await inAppWalletOAuth.LoginWithOauth(
35-
isMobile: false,
36-
(url) =>
37-
{
38-
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
39-
_ = Process.Start(psi);
40-
},
41-
"thirdweb://",
42-
new InAppWalletBrowser()
43-
);
44-
}
45-
var inAppWalletOAuthAddress = await inAppWalletOAuth.GetAddress();
46-
Console.WriteLine($"InAppWallet OAuth address: {inAppWalletOAuthAddress}");
31+
// var inAppWalletOAuth = await InAppWallet.Create(client: client, authProvider: AuthProvider.Telegram);
32+
// if (!await inAppWalletOAuth.IsConnected())
33+
// {
34+
// _ = await inAppWalletOAuth.LoginWithOauth(
35+
// isMobile: false,
36+
// (url) =>
37+
// {
38+
// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
39+
// _ = Process.Start(psi);
40+
// },
41+
// "thirdweb://",
42+
// new InAppWalletBrowser()
43+
// );
44+
// }
45+
// var inAppWalletOAuthAddress = await inAppWalletOAuth.GetAddress();
46+
// Console.WriteLine($"InAppWallet OAuth address: {inAppWalletOAuthAddress}");
4747

4848
// var smartWallet = await SmartWallet.Create(privateKeyWallet, 78600);
4949

@@ -124,7 +124,7 @@
124124
// }
125125

126126

127-
// var inAppWallet = await InAppWallet.Create(client: client, email: "firekeeper+awsless@thirdweb.com"); // or email: null, phoneNumber: "+1234567890"
127+
var inAppWallet = await InAppWallet.Create(client: client, email: "firekeeper+otpv2@thirdweb.com"); // or email: null, phoneNumber: "+1234567890"
128128

129129
// var inAppWallet = await InAppWallet.Create(client: client, authprovider: AuthProvider.Google); // or email: null, phoneNumber: "+1234567890"
130130

@@ -150,22 +150,27 @@
150150
// Console.WriteLine($"InAppWallet address: {address}");
151151
// }
152152

153-
// await inAppWallet.SendOTP();
154-
// Console.WriteLine("Please submit the OTP.");
155-
// retry:
156-
// var otp = Console.ReadLine();
157-
// (var inAppWalletAddress, var canRetry) = await inAppWallet.SubmitOTP(otp);
158-
// if (inAppWalletAddress == null && canRetry)
159-
// {
160-
// Console.WriteLine("Please submit the OTP again.");
161-
// goto retry;
162-
// }
163-
// if (inAppWalletAddress == null)
164-
// {
165-
// Console.WriteLine("OTP login failed. Please try again.");
166-
// return;
167-
// }
168-
// Console.WriteLine($"InAppWallet address: {inAppWalletAddress}");
153+
if (await inAppWallet.IsConnected())
154+
{
155+
Console.WriteLine($"InAppWallet address: {await inAppWallet.GetAddress()}");
156+
return;
157+
}
158+
await inAppWallet.SendOTP();
159+
Console.WriteLine("Please submit the OTP.");
160+
retry:
161+
var otp = Console.ReadLine();
162+
(var inAppWalletAddress, var canRetry) = await inAppWallet.SubmitOTP(otp);
163+
if (inAppWalletAddress == null && canRetry)
164+
{
165+
Console.WriteLine("Please submit the OTP again.");
166+
goto retry;
167+
}
168+
if (inAppWalletAddress == null)
169+
{
170+
Console.WriteLine("OTP login failed. Please try again.");
171+
return;
172+
}
173+
Console.WriteLine($"InAppWallet address: {inAppWalletAddress}");
169174
// }
170175

171176
// Prepare a transaction directly, or with Contract.Prepare

Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,15 +456,8 @@ public async Task FetchThirdwebChainDataAsync_ReturnsChainData_WhenResponseIsSuc
456456
Assert.NotNull(chainData.NativeCurrency.Name);
457457
Assert.NotNull(chainData.NativeCurrency.Symbol);
458458
Assert.Equal(18, chainData.NativeCurrency.Decimals);
459-
Assert.NotNull(chainData.Features);
460459
Assert.NotNull(chainData.Faucets);
461460
Assert.NotNull(chainData.Explorers);
462-
Assert.NotNull(chainData.RedFlags);
463-
Assert.Null(chainData.Parent);
464-
465-
chainId = 42161;
466-
chainData = await Utils.FetchThirdwebChainDataAsync(_client, chainId);
467-
Assert.NotNull(chainData.Parent);
468461
}
469462

470463
[Fact(Timeout = 120000)]

Thirdweb/Thirdweb.Utils/ThirdwebChainData.cs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ public class ThirdwebChainData
4545
[JsonProperty("icon")]
4646
public ThirdwebChainIcon Icon { get; set; }
4747

48-
[JsonProperty("features")]
49-
public List<ThirdwebChainFeature> Features { get; set; }
50-
5148
[JsonProperty("faucets")]
5249
public List<object> Faucets { get; set; }
5350

@@ -62,12 +59,6 @@ public class ThirdwebChainData
6259

6360
[JsonProperty("testnet")]
6461
public bool Testnet { get; set; }
65-
66-
[JsonProperty("redFlags")]
67-
public List<object> RedFlags { get; set; }
68-
69-
[JsonProperty("parent")]
70-
public ThirdwebChainParent Parent { get; set; }
7162
}
7263

7364
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
@@ -99,13 +90,6 @@ public class ThirdwebChainIcon
9990
public string Format { get; set; }
10091
}
10192

102-
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
103-
public class ThirdwebChainFeature
104-
{
105-
[JsonProperty("name")]
106-
public string Name { get; set; }
107-
}
108-
10993
public class ThirdwebChainEns
11094
{
11195
[JsonProperty("registry")]
@@ -127,18 +111,6 @@ public class ThirdwebChainExplorer
127111
public ThirdwebChainIcon Icon { get; set; }
128112
}
129113

130-
public class ThirdwebChainParent
131-
{
132-
[JsonProperty("type")]
133-
public string Type { get; set; }
134-
135-
[JsonProperty("chain")]
136-
public string Chain { get; set; }
137-
138-
[JsonProperty("bridges")]
139-
public List<ThirdwebChainBridge> Bridges { get; set; }
140-
}
141-
142114
public class ThirdwebChainBridge
143115
{
144116
[JsonProperty("url")]

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

Lines changed: 10 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -6,123 +6,20 @@ namespace Thirdweb.EWS
66
{
77
internal class AWS
88
{
9-
private const string awsRegion = "us-west-2";
10-
private const string cognitoAppClientId = "2e02ha2ce6du13ldk8pai4h3d0";
11-
private static readonly string cognitoIdentityPoolId = $"{awsRegion}:2ad7ab1e-f48b-48a6-adfa-ac1090689c26";
12-
private static readonly string cognitoUserPoolId = $"{awsRegion}_UFwLcZIpq";
13-
private static readonly string recoverySharePasswordLambdaFunctionName = $"arn:aws:lambda:{awsRegion}:324457261097:function:recovery-share-password-GenerateRecoverySharePassw-bbE5ZbVAToil";
14-
private static readonly string recoverySharePasswordLambdaFunctionNameV2 = "arn:aws:lambda:us-west-2:324457261097:function:lambda-thirdweb-auth-enc-key-prod-ThirdwebAuthEncKeyFunction";
15-
16-
internal static async Task SignUpCognitoUserAsync(string emailAddress, string userName, Type thirdwebHttpClientType)
17-
{
18-
emailAddress ??= "[email protected]";
19-
20-
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
21-
var endpoint = $"https://cognito-idp.{awsRegion}.amazonaws.com/";
22-
var payload = new
23-
{
24-
ClientId = cognitoAppClientId,
25-
Username = userName,
26-
Password = Secrets.Random(12),
27-
UserAttributes = new[] { new { Name = "email", Value = emailAddress } }
28-
};
29-
30-
var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/x-amz-json-1.1");
31-
32-
client.AddHeader("X-Amz-Target", "AWSCognitoIdentityProviderService.SignUp");
33-
34-
var response = await client.PostAsync(endpoint, content).ConfigureAwait(false);
35-
36-
if (!response.IsSuccessStatusCode)
37-
{
38-
var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
39-
throw new Exception($"Sign-up failed: {responseBody}");
40-
}
41-
}
42-
43-
internal static async Task<string> StartCognitoUserAuth(string userName, Type thirdwebHttpClientType)
44-
{
45-
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
46-
var endpoint = $"https://cognito-idp.{awsRegion}.amazonaws.com/";
47-
var payload = new
48-
{
49-
AuthFlow = "CUSTOM_AUTH",
50-
ClientId = cognitoAppClientId,
51-
AuthParameters = new Dictionary<string, string> { { "USERNAME", userName } },
52-
ClientMetadata = new Dictionary<string, string>()
53-
};
54-
55-
var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/x-amz-json-1.1");
56-
57-
client.AddHeader("X-Amz-Target", "AWSCognitoIdentityProviderService.InitiateAuth");
58-
59-
var response = await client.PostAsync(endpoint, content).ConfigureAwait(false);
60-
61-
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
62-
63-
if (!response.IsSuccessStatusCode)
64-
{
65-
var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
66-
if (errorResponse.Type == "UserNotFoundException")
67-
{
68-
return null;
69-
}
70-
throw new Exception($"Authentication initiation failed: {responseContent}");
71-
}
72-
73-
var jsonResponse = JsonConvert.DeserializeObject<StartAuthResponse>(responseContent);
74-
return jsonResponse.Session;
75-
}
76-
77-
internal static async Task<TokenCollection> FinishCognitoUserAuth(string userName, string otp, string sessionId, Type thirdwebHttpClientType)
78-
{
79-
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
80-
var endpoint = $"https://cognito-idp.{awsRegion}.amazonaws.com/";
81-
var payload = new
82-
{
83-
ChallengeName = "CUSTOM_CHALLENGE",
84-
ClientId = cognitoAppClientId,
85-
ChallengeResponses = new Dictionary<string, string> { { "USERNAME", userName }, { "ANSWER", otp } },
86-
Session = sessionId
87-
};
88-
89-
var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/x-amz-json-1.1");
90-
91-
client.AddHeader("X-Amz-Target", "AWSCognitoIdentityProviderService.RespondToAuthChallenge");
92-
93-
var response = await client.PostAsync(endpoint, content).ConfigureAwait(false);
94-
95-
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
96-
97-
if (!response.IsSuccessStatusCode)
98-
{
99-
var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseContent);
100-
if (errorResponse.Type == "NotAuthorizedException")
101-
{
102-
throw new VerificationException("The session expired", false);
103-
}
104-
if (errorResponse.Type == "UserNotFoundException")
105-
{
106-
throw new InvalidOperationException("The user was not found");
107-
}
108-
throw new Exception($"Challenge response failed: {responseContent}");
109-
}
9+
private const string AWS_REGION = "us-west-2";
11010

111-
var jsonResponse = JsonConvert.DeserializeObject<FinishAuthResponse>(responseContent);
112-
var result = jsonResponse.AuthenticationResult ?? throw new VerificationException("The OTP is incorrect", true);
113-
return new TokenCollection(result.AccessToken.ToString(), result.IdToken.ToString(), result.RefreshToken.ToString());
114-
}
11+
private static readonly string recoverySharePasswordLambdaFunctionNameV2 = $"arn:aws:lambda:{AWS_REGION}:324457261097:function:lambda-thirdweb-auth-enc-key-prod-ThirdwebAuthEncKeyFunction";
11512

116-
internal static async Task<MemoryStream> InvokeRecoverySharePasswordLambdaV2Async(string identityId, string token, string invokePayload, Type thirdwebHttpClientType)
13+
internal static async Task<MemoryStream> InvokeRecoverySharePasswordLambdaAsync(string identityId, string token, string invokePayload, Type thirdwebHttpClientType)
11714
{
118-
var credentials = await GetTemporaryCredentialsV2Async(identityId, token, thirdwebHttpClientType).ConfigureAwait(false);
15+
var credentials = await GetTemporaryCredentialsAsync(identityId, token, thirdwebHttpClientType).ConfigureAwait(false);
11916
return await InvokeLambdaWithTemporaryCredentialsAsync(credentials, invokePayload, thirdwebHttpClientType, recoverySharePasswordLambdaFunctionNameV2).ConfigureAwait(false);
12017
}
12118

122-
private static async Task<AwsCredentials> GetTemporaryCredentialsV2Async(string identityId, string token, Type thirdwebHttpClientType)
19+
private static async Task<AwsCredentials> GetTemporaryCredentialsAsync(string identityId, string token, Type thirdwebHttpClientType)
12320
{
12421
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
125-
var endpoint = $"https://cognito-identity.{awsRegion}.amazonaws.com/";
22+
var endpoint = $"https://cognito-identity.{AWS_REGION}.amazonaws.com/";
12623

12724
var payloadForGetCredentials = new { IdentityId = identityId, Logins = new Dictionary<string, string> { { "cognito-identity.amazonaws.com", token } } };
12825

@@ -148,67 +45,9 @@ private static async Task<AwsCredentials> GetTemporaryCredentialsV2Async(string
14845
};
14946
}
15047

151-
internal static async Task<MemoryStream> InvokeRecoverySharePasswordLambdaAsync(string idToken, string invokePayload, Type thirdwebHttpClientType)
152-
{
153-
var credentials = await GetTemporaryCredentialsAsync(idToken, thirdwebHttpClientType).ConfigureAwait(false);
154-
return await InvokeLambdaWithTemporaryCredentialsAsync(credentials, invokePayload, thirdwebHttpClientType, recoverySharePasswordLambdaFunctionName).ConfigureAwait(false);
155-
}
156-
157-
private static async Task<AwsCredentials> GetTemporaryCredentialsAsync(string idToken, Type thirdwebHttpClientType)
158-
{
159-
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
160-
var endpoint = $"https://cognito-identity.{awsRegion}.amazonaws.com/";
161-
162-
var payloadForGetId = new { IdentityPoolId = cognitoIdentityPoolId, Logins = new Dictionary<string, string> { { $"cognito-idp.{awsRegion}.amazonaws.com/{cognitoUserPoolId}", idToken } } };
163-
164-
var content = new StringContent(JsonConvert.SerializeObject(payloadForGetId), Encoding.UTF8, "application/x-amz-json-1.1");
165-
166-
client.AddHeader("X-Amz-Target", "AWSCognitoIdentityService.GetId");
167-
168-
var response = await client.PostAsync(endpoint, content).ConfigureAwait(false);
169-
170-
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
171-
172-
if (!response.IsSuccessStatusCode)
173-
{
174-
throw new Exception($"Failed to get identity ID: {responseContent}");
175-
}
176-
177-
var identityIdResponse = JsonConvert.DeserializeObject<GetIdResponse>(responseContent);
178-
179-
var payloadForGetCredentials = new
180-
{
181-
IdentityId = identityIdResponse.IdentityId,
182-
Logins = new Dictionary<string, string> { { $"cognito-idp.{awsRegion}.amazonaws.com/{cognitoUserPoolId}", idToken } }
183-
};
184-
185-
content = new StringContent(JsonConvert.SerializeObject(payloadForGetCredentials), Encoding.UTF8, "application/x-amz-json-1.1");
186-
187-
client.RemoveHeader("X-Amz-Target");
188-
client.AddHeader("X-Amz-Target", "AWSCognitoIdentityService.GetCredentialsForIdentity");
189-
190-
response = await client.PostAsync(endpoint, content).ConfigureAwait(false);
191-
192-
responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
193-
194-
if (!response.IsSuccessStatusCode)
195-
{
196-
throw new Exception($"Failed to get credentials: {responseContent}");
197-
}
198-
199-
var credentialsResponse = JsonConvert.DeserializeObject<GetCredentialsForIdentityResponse>(responseContent);
200-
201-
return new AwsCredentials
202-
{
203-
AccessKeyId = credentialsResponse.Credentials.AccessKeyId,
204-
SecretAccessKey = credentialsResponse.Credentials.SecretKey,
205-
SessionToken = credentialsResponse.Credentials.SessionToken
206-
};
207-
}
208-
20948
private static async Task<MemoryStream> InvokeLambdaWithTemporaryCredentialsAsync(AwsCredentials credentials, string invokePayload, Type thirdwebHttpClientType, string lambdaFunction)
21049
{
211-
var endpoint = $"https://lambda.{awsRegion}.amazonaws.com/2015-03-31/functions/{lambdaFunction}/invocations";
50+
var endpoint = $"https://lambda.{AWS_REGION}.amazonaws.com/2015-03-31/functions/{lambdaFunction}/invocations";
21251
var requestBody = new StringContent(invokePayload, Encoding.UTF8, "application/json");
21352

21453
var client = thirdwebHttpClientType.GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient;
@@ -219,18 +58,18 @@ private static async Task<MemoryStream> InvokeLambdaWithTemporaryCredentialsAsyn
21958

22059
var canonicalUri = "/2015-03-31/functions/" + Uri.EscapeDataString(lambdaFunction) + "/invocations";
22160
var canonicalQueryString = "";
222-
var canonicalHeaders = $"host:lambda.{awsRegion}.amazonaws.com\nx-amz-date:{amzDate}\n";
61+
var canonicalHeaders = $"host:lambda.{AWS_REGION}.amazonaws.com\nx-amz-date:{amzDate}\n";
22362
var signedHeaders = "host;x-amz-date";
22463

22564
using var sha256 = SHA256.Create();
22665
var payloadHash = ToHexString(sha256.ComputeHash(Encoding.UTF8.GetBytes(invokePayload)));
22766
var canonicalRequest = $"POST\n{canonicalUri}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
22867

22968
var algorithm = "AWS4-HMAC-SHA256";
230-
var credentialScope = $"{dateStamp}/{awsRegion}/lambda/aws4_request";
69+
var credentialScope = $"{dateStamp}/{AWS_REGION}/lambda/aws4_request";
23170
var stringToSign = $"{algorithm}\n{amzDate}\n{credentialScope}\n{ToHexString(sha256.ComputeHash(Encoding.UTF8.GetBytes(canonicalRequest)))}";
23271

233-
var signingKey = GetSignatureKey(credentials.SecretAccessKey, dateStamp, awsRegion, "lambda");
72+
var signingKey = GetSignatureKey(credentials.SecretAccessKey, dateStamp, AWS_REGION, "lambda");
23473
var signature = ToHexString(HMACSHA256(signingKey, stringToSign));
23574

23675
var authorizationHeader = $"{algorithm} Credential={credentials.AccessKeyId}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}";

0 commit comments

Comments
 (0)