mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-04-05 18:09:04 +00:00
Add WEBSITE_BASE_URL environment variable and update email confirmation link
This commit is contained in:
@@ -94,6 +94,7 @@ services:
|
|||||||
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
||||||
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
||||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||||
|
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- devnet
|
- devnet
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ services:
|
|||||||
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
||||||
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
||||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||||
|
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- prodnet
|
- prodnet
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ services:
|
|||||||
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
ACCESS_TOKEN_SECRET: "${ACCESS_TOKEN_SECRET}"
|
||||||
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
REFRESH_TOKEN_SECRET: "${REFRESH_TOKEN_SECRET}"
|
||||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||||
|
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./test-results:/app/test-results
|
- ./test-results:/app/test-results
|
||||||
restart: "no"
|
restart: "no"
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ REFRESH_TOKEN_SECRET=<generated-secret> # Signs long-lived refresh t
|
|||||||
|
|
||||||
# Confirmation token secret (30-minute tokens)
|
# Confirmation token secret (30-minute tokens)
|
||||||
CONFIRMATION_TOKEN_SECRET=<generated-secret> # Signs email confirmation tokens
|
CONFIRMATION_TOKEN_SECRET=<generated-secret> # Signs email confirmation tokens
|
||||||
|
|
||||||
|
# Website base URL (used in confirmation emails)
|
||||||
|
WEBSITE_BASE_URL=https://thebiergarten.app # Base URL for the website
|
||||||
```
|
```
|
||||||
|
|
||||||
**Security Requirements**:
|
**Security Requirements**:
|
||||||
@@ -292,6 +295,7 @@ touch .env.local
|
|||||||
| `ACCESS_TOKEN_SECRET` | ✓ | | ✓ | Yes | Access token secret |
|
| `ACCESS_TOKEN_SECRET` | ✓ | | ✓ | Yes | Access token secret |
|
||||||
| `REFRESH_TOKEN_SECRET` | ✓ | | ✓ | Yes | Refresh token secret |
|
| `REFRESH_TOKEN_SECRET` | ✓ | | ✓ | Yes | Refresh token secret |
|
||||||
| `CONFIRMATION_TOKEN_SECRET` | ✓ | | ✓ | Yes | Confirmation token secret |
|
| `CONFIRMATION_TOKEN_SECRET` | ✓ | | ✓ | Yes | Confirmation token secret |
|
||||||
|
| `WEBSITE_BASE_URL` | ✓ | | | Yes | Website URL for emails |
|
||||||
| **Authentication (Frontend)** |
|
| **Authentication (Frontend)** |
|
||||||
| `CONFIRMATION_TOKEN_SECRET` | | ✓ | | Yes | Email confirmation |
|
| `CONFIRMATION_TOKEN_SECRET` | | ✓ | | Yes | Email confirmation |
|
||||||
| `RESET_PASSWORD_TOKEN_SECRET` | | ✓ | | Yes | Password reset |
|
| `RESET_PASSWORD_TOKEN_SECRET` | | ✓ | | Yes | Password reset |
|
||||||
@@ -359,6 +363,7 @@ DB_PASSWORD=Dev_Password_123!
|
|||||||
ACCESS_TOKEN_SECRET=<generated-with-openssl>
|
ACCESS_TOKEN_SECRET=<generated-with-openssl>
|
||||||
REFRESH_TOKEN_SECRET=<generated-with-openssl>
|
REFRESH_TOKEN_SECRET=<generated-with-openssl>
|
||||||
CONFIRMATION_TOKEN_SECRET=<generated-with-openssl>
|
CONFIRMATION_TOKEN_SECRET=<generated-with-openssl>
|
||||||
|
WEBSITE_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
CLEAR_DATABASE=true
|
CLEAR_DATABASE=true
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using API.Core.Contracts.Auth;
|
using API.Core.Contracts.Auth;
|
||||||
using API.Core.Contracts.Common;
|
using API.Core.Contracts.Common;
|
||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Service.Auth;
|
using Service.Auth;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ namespace API.Core.Controllers
|
|||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Authorize(AuthenticationSchemes = "JWT")]
|
||||||
public class AuthController(
|
public class AuthController(
|
||||||
IRegisterService registerService,
|
IRegisterService registerService,
|
||||||
ILoginService loginService,
|
ILoginService loginService,
|
||||||
@@ -15,6 +17,7 @@ namespace API.Core.Controllers
|
|||||||
ITokenService tokenService
|
ITokenService tokenService
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
|
[AllowAnonymous]
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<ActionResult<UserAccount>> Register(
|
public async Task<ActionResult<UserAccount>> Register(
|
||||||
[FromBody] RegisterRequest req
|
[FromBody] RegisterRequest req
|
||||||
@@ -47,6 +50,7 @@ namespace API.Core.Controllers
|
|||||||
return Created("/", response);
|
return Created("/", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<ActionResult> Login([FromBody] LoginRequest req)
|
public async Task<ActionResult> Login([FromBody] LoginRequest req)
|
||||||
{
|
{
|
||||||
@@ -82,6 +86,7 @@ namespace API.Core.Controllers
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
[HttpPost("refresh")]
|
[HttpPost("refresh")]
|
||||||
public async Task<ActionResult> Refresh(
|
public async Task<ActionResult> Refresh(
|
||||||
[FromBody] RefreshTokenRequest req
|
[FromBody] RefreshTokenRequest req
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Feature: User Account Confirmation
|
|||||||
Given the API is running
|
Given the API is running
|
||||||
And I have registered a new account
|
And I have registered a new account
|
||||||
And I have a valid confirmation token for my account
|
And I have a valid confirmation token for my account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with the valid token
|
When I submit a confirmation request with the valid token
|
||||||
Then the response has HTTP status 200
|
Then the response has HTTP status 200
|
||||||
And the response JSON should have "message" containing "is confirmed"
|
And the response JSON should have "message" containing "is confirmed"
|
||||||
@@ -14,6 +15,7 @@ Feature: User Account Confirmation
|
|||||||
Given the API is running
|
Given the API is running
|
||||||
And I have registered a new account
|
And I have registered a new account
|
||||||
And I have a valid confirmation token for my account
|
And I have a valid confirmation token for my account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with the valid token
|
When I submit a confirmation request with the valid token
|
||||||
And I submit the same confirmation request again
|
And I submit the same confirmation request again
|
||||||
Then the response has HTTP status 200
|
Then the response has HTTP status 200
|
||||||
@@ -21,6 +23,8 @@ Feature: User Account Confirmation
|
|||||||
|
|
||||||
Scenario: Confirmation fails with invalid token
|
Scenario: Confirmation fails with invalid token
|
||||||
Given the API is running
|
Given the API is running
|
||||||
|
And I have registered a new account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with an invalid token
|
When I submit a confirmation request with an invalid token
|
||||||
Then the response has HTTP status 401
|
Then the response has HTTP status 401
|
||||||
And the response JSON should have "message" containing "Invalid token"
|
And the response JSON should have "message" containing "Invalid token"
|
||||||
@@ -29,6 +33,7 @@ Feature: User Account Confirmation
|
|||||||
Given the API is running
|
Given the API is running
|
||||||
And I have registered a new account
|
And I have registered a new account
|
||||||
And I have an expired confirmation token for my account
|
And I have an expired confirmation token for my account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with the expired token
|
When I submit a confirmation request with the expired token
|
||||||
Then the response has HTTP status 401
|
Then the response has HTTP status 401
|
||||||
And the response JSON should have "message" containing "Invalid token"
|
And the response JSON should have "message" containing "Invalid token"
|
||||||
@@ -37,12 +42,15 @@ Feature: User Account Confirmation
|
|||||||
Given the API is running
|
Given the API is running
|
||||||
And I have registered a new account
|
And I have registered a new account
|
||||||
And I have a confirmation token signed with the wrong secret
|
And I have a confirmation token signed with the wrong secret
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with the tampered token
|
When I submit a confirmation request with the tampered token
|
||||||
Then the response has HTTP status 401
|
Then the response has HTTP status 401
|
||||||
And the response JSON should have "message" containing "Invalid token"
|
And the response JSON should have "message" containing "Invalid token"
|
||||||
|
|
||||||
Scenario: Confirmation fails when token is missing
|
Scenario: Confirmation fails when token is missing
|
||||||
Given the API is running
|
Given the API is running
|
||||||
|
And I have registered a new account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with a missing token
|
When I submit a confirmation request with a missing token
|
||||||
Then the response has HTTP status 400
|
Then the response has HTTP status 400
|
||||||
|
|
||||||
@@ -54,6 +62,15 @@ Feature: User Account Confirmation
|
|||||||
|
|
||||||
Scenario: Confirmation fails with malformed token
|
Scenario: Confirmation fails with malformed token
|
||||||
Given the API is running
|
Given the API is running
|
||||||
|
And I have registered a new account
|
||||||
|
And I have a valid access token for my account
|
||||||
When I submit a confirmation request with a malformed token
|
When I submit a confirmation request with a malformed token
|
||||||
Then the response has HTTP status 401
|
Then the response has HTTP status 401
|
||||||
And the response JSON should have "message" containing "Invalid token"
|
And the response JSON should have "message" containing "Invalid token"
|
||||||
|
|
||||||
|
Scenario: Confirmation fails without an access token
|
||||||
|
Given the API is running
|
||||||
|
And I have registered a new account
|
||||||
|
And I have a valid confirmation token for my account
|
||||||
|
When I submit a confirmation request with the valid token without an access token
|
||||||
|
Then the response has HTTP status 401
|
||||||
|
|||||||
@@ -457,6 +457,32 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
await GivenIAmLoggedIn();
|
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")]
|
[Given("I have a valid confirmation token for my account")]
|
||||||
public void GivenIHaveAValidConfirmationTokenForMyAccount()
|
public void GivenIHaveAValidConfirmationTokenForMyAccount()
|
||||||
{
|
{
|
||||||
@@ -587,11 +613,16 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
||||||
? t
|
? t
|
||||||
: "valid-token";
|
: "valid-token";
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
||||||
);
|
);
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
var response = await client.SendAsync(requestMessage);
|
var response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -606,11 +637,16 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
||||||
? t
|
? t
|
||||||
: "valid-token";
|
: "valid-token";
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
||||||
);
|
);
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
var response = await client.SendAsync(requestMessage);
|
var response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -623,11 +659,16 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
{
|
{
|
||||||
var client = GetClient();
|
var client = GetClient();
|
||||||
const string token = "malformed-token-not-jwt";
|
const string token = "malformed-token-not-jwt";
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
||||||
);
|
);
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
var response = await client.SendAsync(requestMessage);
|
var response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -883,11 +924,16 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
||||||
? t
|
? t
|
||||||
: "expired-confirmation-token";
|
: "expired-confirmation-token";
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
||||||
);
|
);
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
var response = await client.SendAsync(requestMessage);
|
var response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -902,11 +948,16 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
var token = scenario.TryGetValue<string>("confirmationToken", out var t)
|
||||||
? t
|
? t
|
||||||
: "tampered-confirmation-token";
|
: "tampered-confirmation-token";
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
$"/api/auth/confirm?token={Uri.EscapeDataString(token)}"
|
||||||
);
|
);
|
||||||
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
|
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
var response = await client.SendAsync(requestMessage);
|
var response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -918,7 +969,13 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
public async Task WhenISubmitAConfirmationRequestWithAMissingToken()
|
public async Task WhenISubmitAConfirmationRequestWithAMissingToken()
|
||||||
{
|
{
|
||||||
var client = GetClient();
|
var client = GetClient();
|
||||||
|
var accessToken = scenario.TryGetValue<string>("accessToken", out var at)
|
||||||
|
? at
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/auth/confirm");
|
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 response = await client.SendAsync(requestMessage);
|
||||||
var responseBody = await response.Content.ReadAsStringAsync();
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
@@ -974,6 +1031,30 @@ public class AuthSteps(ScenarioContext scenario)
|
|||||||
{
|
{
|
||||||
var client = GetClient();
|
var client = GetClient();
|
||||||
const string token = "invalid-confirmation-token";
|
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(
|
var requestMessage = new HttpRequestMessage(
|
||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
|
|||||||
@@ -17,13 +17,17 @@ public class EmailService(
|
|||||||
IEmailTemplateProvider emailTemplateProvider
|
IEmailTemplateProvider emailTemplateProvider
|
||||||
) : IEmailService
|
) : IEmailService
|
||||||
{
|
{
|
||||||
|
private static readonly string WebsiteBaseUrl =
|
||||||
|
Environment.GetEnvironmentVariable("WEBSITE_BASE_URL")
|
||||||
|
?? throw new InvalidOperationException("WEBSITE_BASE_URL environment variable is not set");
|
||||||
|
|
||||||
public async Task SendRegistrationEmailAsync(
|
public async Task SendRegistrationEmailAsync(
|
||||||
UserAccount createdUser,
|
UserAccount createdUser,
|
||||||
string confirmationToken
|
string confirmationToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var confirmationLink =
|
var confirmationLink =
|
||||||
$"https://thebiergarten.app/confirm?token={confirmationToken}";
|
$"{WebsiteBaseUrl}/users/confirm?token={confirmationToken}";
|
||||||
|
|
||||||
var emailHtml =
|
var emailHtml =
|
||||||
await emailTemplateProvider.RenderUserRegisteredEmailAsync(
|
await emailTemplateProvider.RenderUserRegisteredEmailAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user