Files
the-biergarten-app/src/Core/API/API.Specs/Steps/AuthSteps.cs
2026-03-07 23:03:31 -05:00

1215 lines
40 KiB
C#

using System.Text.Json;
using API.Specs;
using FluentAssertions;
using Infrastructure.Jwt;
using Reqnroll;
namespace API.Specs.Steps;
[Binding]
public class AuthSteps(ScenarioContext scenario)
{
private const string ClientKey = "client";
private const string FactoryKey = "factory";
private const string ResponseKey = "response";
private const string ResponseBodyKey = "responseBody";
private const string TestUserKey = "testUser";
private const string RegisteredUserIdKey = "registeredUserId";
private const string RegisteredUsernameKey = "registeredUsername";
private const string PreviousAccessTokenKey = "previousAccessToken";
private const string PreviousRefreshTokenKey = "previousRefreshToken";
private HttpClient GetClient()
{
if (scenario.TryGetValue<HttpClient>(ClientKey, out var client))
{
return client;
}
var factory = scenario.TryGetValue<TestApiFactory>(
FactoryKey,
out var f
)
? f
: new TestApiFactory();
scenario[FactoryKey] = factory;
client = factory.CreateClient();
scenario[ClientKey] = client;
return client;
}
private static string GetRequiredEnvVar(string name)
{
return Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException(
$"{name} environment variable is not set"
);
}
private static string GenerateJwtToken(
Guid userId,
string username,
string secret,
DateTime expiry
)
{
var infra = new JwtInfrastructure();
return infra.GenerateJwt(userId, username, expiry, secret);
}
private static Guid ParseRegisteredUserId(JsonElement root)
{
return root
.GetProperty("payload")
.GetProperty("userAccountId")
.GetGuid();
}
private static string ParseRegisteredUsername(JsonElement root)
{
return root
.GetProperty("payload")
.GetProperty("username")
.GetString()
?? throw new InvalidOperationException(
"username missing from registration payload"
);
}
private static string ParseTokenFromPayload(
JsonElement payload,
string camelCaseName,
string pascalCaseName
)
{
if (
payload.TryGetProperty(camelCaseName, out var tokenElem)
|| payload.TryGetProperty(pascalCaseName, out tokenElem)
)
{
return tokenElem.GetString()
?? throw new InvalidOperationException(
$"{camelCaseName} is null"
);
}
throw new InvalidOperationException(
$"Could not find token field '{camelCaseName}' in payload"
);
}
[Given("I have an existing account")]
public void GivenIHaveAnExistingAccount()
{
scenario[TestUserKey] = ("test.user", "password");
}
[Given("I do not have an existing account")]
public void GivenIDoNotHaveAnExistingAccount()
{
scenario[TestUserKey] = ("Failing", "User");
}
[When("I submit a login request with a username and password")]
public async Task WhenISubmitALoginRequestWithAUsernameAndPassword()
{
var client = GetClient();
var (username, password) = scenario.TryGetValue<(
string username,
string password
)>(TestUserKey, out var user)
? user
: ("test.user", "password");
var body = JsonSerializer.Serialize(new { username, password });
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a login request with a missing username")]
public async Task WhenISubmitALoginRequestWithAMissingUsername()
{
var client = GetClient();
var body = JsonSerializer.Serialize(new { password = "test" });
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a login request with a missing password")]
public async Task WhenISubmitALoginRequestWithAMissingPassword()
{
var client = GetClient();
var body = JsonSerializer.Serialize(new { username = "test" });
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a login request with both username and password missing")]
public async Task WhenISubmitALoginRequestWithBothUsernameAndPasswordMissing()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
"{}",
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Then("the response JSON should have an access token")]
public void ThenTheResponseJsonShouldHaveAnAccessToken()
{
scenario
.TryGetValue<HttpResponseMessage>(ResponseKey, out var response)
.Should()
.BeTrue();
scenario
.TryGetValue<string>(ResponseBodyKey, out var responseBody)
.Should()
.BeTrue();
var doc = JsonDocument.Parse(responseBody!);
var root = doc.RootElement;
JsonElement tokenElem = default;
var hasToken = false;
if (
root.TryGetProperty("payload", out var payloadElem)
&& payloadElem.ValueKind == JsonValueKind.Object
)
{
hasToken =
payloadElem.TryGetProperty("accessToken", out tokenElem)
|| payloadElem.TryGetProperty("AccessToken", out tokenElem);
}
hasToken
.Should()
.BeTrue(
"Expected an access token either at the root or inside 'payload'"
);
var token = tokenElem.GetString();
token.Should().NotBeNullOrEmpty();
}
[When("I submit a login request using a GET request")]
public async Task WhenISubmitALoginRequestUsingAgetRequest()
{
var client = GetClient();
// testing GET
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/auth/login"
)
{
Content = new StringContent(
"{}",
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a registration request with values:")]
public async Task WhenISubmitARegistrationRequestWithValues(Table table)
{
var client = GetClient();
var row = table.Rows[0];
var username = row["Username"] ?? "";
var firstName = row["FirstName"] ?? "";
var lastName = row["LastName"] ?? "";
var email = row["Email"] ?? "";
var dateOfBirth = row["DateOfBirth"] ?? "";
if (dateOfBirth == "{underage_date}")
{
dateOfBirth = DateTime.UtcNow.AddYears(-18).ToString("yyyy-MM-dd");
}
// Keep default registration fixture values unique across repeated runs.
if (email == "newuser@example.com")
{
var suffix = Guid.NewGuid().ToString("N")[..8];
email = $"newuser-{suffix}@example.com";
if (username == "newuser")
{
username = $"newuser-{suffix}";
}
}
var password = row["Password"];
var registrationData = new
{
username,
firstName,
lastName,
email,
dateOfBirth,
password,
};
var body = JsonSerializer.Serialize(registrationData);
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/register"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a registration request using a GET request")]
public async Task WhenISubmitARegistrationRequestUsingAGetRequest()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/auth/register"
)
{
Content = new StringContent(
"{}",
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Given("I have registered a new account")]
public async Task GivenIHaveRegisteredANewAccount()
{
var client = GetClient();
var suffix = Guid.NewGuid().ToString("N")[..8];
var registrationData = new
{
username = $"newuser-{suffix}",
firstName = "New",
lastName = "User",
email = $"newuser-{suffix}@example.com",
dateOfBirth = "1990-01-01",
password = "Password1!",
};
var body = JsonSerializer.Serialize(registrationData);
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/register"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
using var doc = JsonDocument.Parse(responseBody);
var root = doc.RootElement;
scenario[RegisteredUserIdKey] = ParseRegisteredUserId(root);
scenario[RegisteredUsernameKey] = ParseRegisteredUsername(root);
}
[Given("I am logged in")]
public async Task GivenIAmLoggedIn()
{
var client = GetClient();
var loginData = new { username = "test.user", password = "password" };
var body = JsonSerializer.Serialize(loginData);
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/login"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(responseBody);
var root = doc.RootElement;
if (root.TryGetProperty("payload", out var payloadElem))
{
if (
payloadElem.TryGetProperty("accessToken", out var tokenElem)
|| payloadElem.TryGetProperty("AccessToken", out tokenElem)
)
{
scenario["accessToken"] = tokenElem.GetString();
}
if (
payloadElem.TryGetProperty("refreshToken", out var refreshElem)
|| payloadElem.TryGetProperty("RefreshToken", out refreshElem)
)
{
scenario["refreshToken"] = refreshElem.GetString();
}
}
}
[Given("I have a valid refresh token")]
public async Task GivenIHaveAValidRefreshToken()
{
await GivenIAmLoggedIn();
}
[Given("I am logged in with an immediately-expiring refresh token")]
public async Task GivenIAmLoggedInWithAnImmediatelyExpiringRefreshToken()
{
// For now, create a normal login; in production this would generate an expiring token
await GivenIAmLoggedIn();
}
[Given("I have a valid access token for my account")]
public void GivenIHaveAValidAccessTokenForMyAccount()
{
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
var username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out var user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
var secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET");
scenario["accessToken"] = GenerateJwtToken(
userId,
username,
secret,
DateTime.UtcNow.AddMinutes(60)
);
}
[Given("I have a valid confirmation token for my account")]
public void GivenIHaveAValidConfirmationTokenForMyAccount()
{
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
var username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out var user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
var secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
secret,
DateTime.UtcNow.AddMinutes(5)
);
}
[Given("I have an expired confirmation token for my account")]
public void GivenIHaveAnExpiredConfirmationTokenForMyAccount()
{
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
var username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out var user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
var secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
secret,
DateTime.UtcNow.AddMinutes(-5)
);
}
[Given("I have a confirmation token signed with the wrong secret")]
public void GivenIHaveAConfirmationTokenSignedWithTheWrongSecret()
{
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: throw new InvalidOperationException(
"registered user ID not found in scenario"
);
var username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out var user
)
? user
: throw new InvalidOperationException(
"registered username not found in scenario"
);
const string wrongSecret =
"wrong-confirmation-secret-that-is-very-long-1234567890";
scenario["confirmationToken"] = GenerateJwtToken(
userId,
username,
wrongSecret,
DateTime.UtcNow.AddMinutes(5)
);
}
[When(
"I submit a request to a protected endpoint with a valid access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAValidAccessToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("accessToken", out var t)
? t
: "invalid-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", $"Bearer {token}" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When(
"I submit a request to a protected endpoint with an invalid access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAnInvalidAccessToken()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", "Bearer invalid-token-format" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with the valid token")]
public async Task WhenISubmitAConfirmationRequestWithTheValidToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "valid-token";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit the same confirmation request again")]
public async Task WhenISubmitTheSameConfirmationRequestAgain()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "valid-token";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with a malformed token")]
public async Task WhenISubmitAConfirmationRequestWithAMalformedToken()
{
var client = GetClient();
const string token = "malformed-token-not-jwt";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a refresh token request with a valid refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithTheValidRefreshToken()
{
var client = GetClient();
if (scenario.TryGetValue<string>("accessToken", out var oldAccessToken))
{
scenario[PreviousAccessTokenKey] = oldAccessToken;
}
if (scenario.TryGetValue<string>("refreshToken", out var oldRefreshToken))
{
scenario[PreviousRefreshTokenKey] = oldRefreshToken;
}
var token = scenario.TryGetValue<string>("refreshToken", out var t)
? t
: "valid-refresh-token";
var body = JsonSerializer.Serialize(new { refreshToken = token });
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a refresh token request with an invalid refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithAnInvalidRefreshToken()
{
var client = GetClient();
var body = JsonSerializer.Serialize(
new { refreshToken = "invalid-refresh-token" }
);
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a refresh token request with the expired refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithTheExpiredRefreshToken()
{
var client = GetClient();
// Use an expired token
var body = JsonSerializer.Serialize(
new { refreshToken = "expired-refresh-token" }
);
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a refresh token request with a missing refresh token")]
public async Task WhenISubmitARefreshTokenRequestWithAMissingRefreshToken()
{
var client = GetClient();
var body = JsonSerializer.Serialize(new { });
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
"/api/auth/refresh"
)
{
Content = new StringContent(
body,
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a refresh token request using a GET request")]
public async Task WhenISubmitARefreshTokenRequestUsingAGETRequest()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/auth/refresh"
)
{
Content = new StringContent(
"{}",
System.Text.Encoding.UTF8,
"application/json"
),
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
// Protected Endpoint Steps
[When("I submit a request to a protected endpoint without an access token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithoutAnAccessToken()
{
var client = GetClient();
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
);
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Given("I am logged in with an immediately-expiring access token")]
public Task GivenIAmLoggedInWithAnImmediatelyExpiringAccessToken()
{
// Simulate an expired access token for auth rejection behavior.
scenario["accessToken"] = "expired-access-token";
return Task.CompletedTask;
}
[Given("I have an access token signed with the wrong secret")]
public void GivenIHaveAnAccessTokenSignedWithTheWrongSecret()
{
// Create a token with a different secret
scenario["accessToken"] =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
}
[When("I submit a request to a protected endpoint with the expired token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithTheExpiredToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("accessToken", out var t)
? t
: "expired-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", $"Bearer {token}" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a request to a protected endpoint with the tampered token")]
public async Task WhenISubmitARequestToAProtectedEndpointWithTheTamperedToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("accessToken", out var t)
? t
: "tampered-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", $"Bearer {token}" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When(
"I submit a request to a protected endpoint with my refresh token instead of access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithMyRefreshTokenInsteadOfAccessToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("refreshToken", out var t)
? t
: "refresh-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", $"Bearer {token}" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Given("I have a valid confirmation token")]
public void GivenIHaveAValidConfirmationToken()
{
scenario["confirmationToken"] = "valid-confirmation-token";
}
[When("I submit a confirmation request with the expired token")]
public async Task WhenISubmitAConfirmationRequestWithTheExpiredToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "expired-confirmation-token";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with the tampered token")]
public async Task WhenISubmitAConfirmationRequestWithTheTamperedToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "tampered-confirmation-token";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with a missing token")]
public async Task WhenISubmitAConfirmationRequestWithAMissingToken()
{
var client = GetClient();
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/auth/confirm");
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request using an invalid HTTP method")]
public async Task WhenISubmitAConfirmationRequestUsingAnInvalidHttpMethod()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "valid-confirmation-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When(
"I submit a request to a protected endpoint with my confirmation token instead of access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithMyConfirmationTokenInsteadOfAccessToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "confirmation-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
"/api/protected"
)
{
Headers = { { "Authorization", $"Bearer {token}" } },
};
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with an invalid token")]
public async Task WhenISubmitAConfirmationRequestWithAnInvalidToken()
{
var client = GetClient();
const string token = "invalid-confirmation-token";
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a confirmation request with the valid token without an access token")]
public async Task WhenISubmitAConfirmationRequestWithTheValidTokenWithoutAnAccessToken()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: "valid-token";
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[Then("the response JSON should have a new access token")]
public void ThenTheResponseJsonShouldHaveANewAccessToken()
{
scenario
.TryGetValue<string>(ResponseBodyKey, out var responseBody)
.Should()
.BeTrue();
using var doc = JsonDocument.Parse(responseBody!);
var payload = doc.RootElement.GetProperty("payload");
var accessToken = ParseTokenFromPayload(
payload,
"accessToken",
"AccessToken"
);
accessToken.Should().NotBeNullOrWhiteSpace();
if (
scenario.TryGetValue<string>(
PreviousAccessTokenKey,
out var previousAccessToken
)
)
{
accessToken.Should().NotBe(previousAccessToken);
}
}
[Then("the response JSON should have a new refresh token")]
public void ThenTheResponseJsonShouldHaveANewRefreshToken()
{
scenario
.TryGetValue<string>(ResponseBodyKey, out var responseBody)
.Should()
.BeTrue();
using var doc = JsonDocument.Parse(responseBody!);
var payload = doc.RootElement.GetProperty("payload");
var refreshToken = ParseTokenFromPayload(
payload,
"refreshToken",
"RefreshToken"
);
refreshToken.Should().NotBeNullOrWhiteSpace();
if (
scenario.TryGetValue<string>(
PreviousRefreshTokenKey,
out var previousRefreshToken
)
)
{
refreshToken.Should().NotBe(previousRefreshToken);
}
}
[Given("I have confirmed my account")]
public async Task GivenIHaveConfirmedMyAccount()
{
var client = GetClient();
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
? t
: throw new InvalidOperationException("confirmation token not found");
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
}
[When("I submit a resend confirmation request for my account")]
public async Task WhenISubmitAResendConfirmationRequestForMyAccount()
{
var client = GetClient();
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: throw new InvalidOperationException("registered user ID not found");
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={userId}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a resend confirmation request for a non-existent user")]
public async Task WhenISubmitAResendConfirmationRequestForANonExistentUser()
{
var client = GetClient();
var fakeUserId = Guid.NewGuid();
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
? at
: string.Empty;
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={fakeUserId}"
);
if (!string.IsNullOrEmpty(accessToken))
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
[When("I submit a resend confirmation request without an access token")]
public async Task WhenISubmitAResendConfirmationRequestWithoutAnAccessToken()
{
var client = GetClient();
var userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out var id)
? id
: Guid.NewGuid();
var requestMessage = new HttpRequestMessage(
HttpMethod.Post,
$"/api/auth/confirm/resend?userId={userId}"
);
var response = await client.SendAsync(requestMessage);
var responseBody = await response.Content.ReadAsStringAsync();
scenario[ResponseKey] = response;
scenario[ResponseBodyKey] = responseBody;
}
}