Skip to content

Commit

Permalink
integrate export user data into settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
josxha committed Mar 20, 2024
1 parent 66dca78 commit 2914ba5
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 25 deletions.
21 changes: 1 addition & 20 deletions KratosSelfService/Controllers/ProfileController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@

namespace KratosSelfService.Controllers;

public class ProfileController(
IdentitySchemaService schemaService, EnvService envService, ILogger logger) : Controller
public class ProfileController(IdentitySchemaService schemaService) : Controller
{
private HttpClient _httpClient = new();

[HttpGet("")]
public async Task<IActionResult> Profile()
{
Expand All @@ -20,20 +17,4 @@ public async Task<IActionResult> Profile()
session.Identity.SchemaUrl);
return View("Profile", new ProfileModel(session, IdentitySchemaService.GetTraits(schema)));
}

[HttpGet("export-data")]
public async Task<IActionResult> ExportData(CancellationToken cancellationToken)
{
var session = HttpContext.GetSession()!;
if (string.IsNullOrWhiteSpace(envService.ExportUserDataUrl))
{
logger.LogDebug("Called disabled export-data endpoint, return 404.");
return NotFound();
}
var url = envService.ExportUserDataUrl.Replace("{Id}", session.Identity.Id);
var stream = await _httpClient.GetStreamAsync(url, cancellationToken:cancellationToken);
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
return File(stream, "APPLICATION/octet-stream",
$"user-export-{timestamp}.zip");
}
}
84 changes: 80 additions & 4 deletions KratosSelfService/Controllers/SettingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
namespace KratosSelfService.Controllers;

[Route("settings")]
public class SettingsController(ILogger<SettingsController> logger, ApiService api) : Controller
public class SettingsController(
ILogger<SettingsController> logger,
ApiService api,
EnvService envService) : Controller
{
private readonly HttpClient _httpClient = new();

[HttpGet("")]
public async Task<IActionResult> Settings(
[FromQuery(Name = "flow")] Guid? flowId,
Expand Down Expand Up @@ -38,7 +43,7 @@ public async Task<IActionResult> Settings(
return Redirect($"/settings?flow={newFlow.Id}");
}

var model = new SettingsModel(flow);
var model = new SettingsModel(flow, !string.IsNullOrWhiteSpace(envService.ExportUserDataUrl));
return View("Settings", model);
}

Expand All @@ -65,7 +70,8 @@ public async Task<IActionResult> DeleteAccount(
return Redirect($"/settings?return_to={returnTo}");
}

return View("DeleteAccount", new SettingsModel(flow));
var model = new SettingsModel(flow, !string.IsNullOrWhiteSpace(envService.ExportUserDataUrl));
return View("DeleteAccount", model);
}

[HttpPost("delete-account")]
Expand All @@ -90,10 +96,80 @@ public async Task<IActionResult> ConfirmAccountDeletion(
logger.LogDebug("Couldn't retrieve flow for provided flowId: {Message}", exception.Message);
return Redirect($"/settings?return_to={returnTo}");
}

var session = HttpContext.GetSession()!;
// TODO require the user to reauthenticate!
await api.KratosIdentity.DeleteIdentityAsync(session.Identity.Id);
return Redirect("/login");
}

[HttpGet("export-data")]
public async Task<IActionResult> ExportData(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery(Name = "return_to")] string? returnTo)
{
if (string.IsNullOrWhiteSpace(envService.ExportUserDataUrl))
{
logger.LogDebug("Called disabled export-data endpoint, return 404.");
return NotFound();
}
if (flowId == null)
{
// init new flow
logger.LogDebug("No flow ID found in URL query");
return Redirect($"/settings?return_to={returnTo}");
}

KratosSettingsFlow flow;
try
{
flow = await api.Frontend.GetSettingsFlowAsync(flowId.ToString(), cookie: Request.Headers.Cookie);
}
catch (ApiException exception)
{
logger.LogDebug("Couldn't retrieve flow for provided flowId: {Message}", exception.Message);
return Redirect($"/settings?return_to={returnTo}");
}

return View("ExportData", new SettingsModel(flow, true));
}

[HttpPost("export-data")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ConfirmExportData(
[FromQuery(Name = "flow")] Guid? flowId,
[FromQuery(Name = "return_to")] string? returnTo,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(envService.ExportUserDataUrl))
{
logger.LogDebug("Called disabled export-data endpoint, return 404.");
return NotFound();
}

if (flowId == null)
{
// init new flow
logger.LogDebug("No flow ID found in URL query");
return Redirect($"/settings?return_to={returnTo}");
}

try
{
_ = await api.Frontend.GetSettingsFlowAsync(flowId.ToString(),
cookie: Request.Headers.Cookie, cancellationToken: cancellationToken);
}
catch (ApiException exception)
{
logger.LogDebug("Couldn't retrieve flow for provided flowId: {Message}", exception.Message);
return Redirect($"/settings?return_to={returnTo}");
}

var session = HttpContext.GetSession()!;
var url = envService.ExportUserDataUrl.Replace("{Id}", session.Identity.Id);
var stream = await _httpClient.GetStreamAsync(url, cancellationToken: cancellationToken);
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
return File(stream, "APPLICATION/octet-stream",
$"user-export-{timestamp}.zip");
}
}
3 changes: 2 additions & 1 deletion KratosSelfService/Models/models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ List<KratosSession> OtherSessions
);

public record SettingsModel(
KratosSettingsFlow flow
KratosSettingsFlow flow,
bool exportUserDataEnabled
);

public record VerificationModel(
Expand Down
32 changes: 32 additions & 0 deletions KratosSelfService/Views/Settings/ExportData.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@model SettingsModel
@{
Layout = "_CardLayout";
ViewData["Title"] = CustomTranslator.Get("exportUserData.title");
}

<h1 class="title has-text-centered">@CustomTranslator.Get("exportUserData.title")</h1>
<div class="mb-2">
<div class="message is-info p-3">
@CustomTranslator.Get("exportUserData.info")
</div>
</div>
<hr/>

<form class="mb-3" action="/settings/[email protected]&[email protected]" method="post">
@Html.AntiForgeryToken()
<div class="field">
<div class="columns is-mobile is-multiline">
<div class="column is-half">
<a class="button is-dark is-fullwidth"
href="/[email protected]&[email protected]">
@CustomTranslator.Get("exportUserData.goBack")
</a>
</div>
<div class="column is-half">
<button type="submit" class="button is-success is-fullwidth">
@CustomTranslator.Get("exportUserData.confirm")
</button>
</div>
</div>
</div>
</form>
17 changes: 17 additions & 0 deletions KratosSelfService/Views/Settings/Settings.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@
</form>
</div>
}
@if (Model.exportUserDataEnabled) {
<div class="box p-5" id="export-data">
<h3 class="subtitle">
@CustomTranslator.Get("settings.exportData")
</h3>
<div class="mb-3">
<div class="field">
<div class="control">
<a href="settings/[email protected]&[email protected]"
class="button is-info">
@CustomTranslator.Get("settings.exportDataButtonLabel")
</a>
</div>
</div>
</div>
</div>
}
<div class="box p-5" id="delete-account">
<h3 class="subtitle">
@CustomTranslator.Get("settings.deleteAccount")
Expand Down

0 comments on commit 2914ba5

Please sign in to comment.