Skip to content

Commit 7b6e895

Browse files
committed
Add Identity Auth Pages
1 parent 3b992d6 commit 7b6e895

File tree

99 files changed

+9985
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+9985
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.AspNetCore.Identity;
2+
using Microsoft.AspNetCore.Identity.UI.Services;
3+
using MyApp.Data;
4+
5+
namespace MyApp.ServiceInterface;
6+
7+
// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation.
8+
public sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser>
9+
{
10+
private readonly IEmailSender emailSender = new NoOpEmailSender();
11+
12+
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
13+
emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
14+
15+
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
16+
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
17+
18+
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
19+
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}");
20+
}

MyApp.ServiceModel/Bookings.d.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/// <reference path="./api.d.ts" />
2+
export type Config = {
3+
prompt: "New Booking"
4+
api: "~/MyApp.ServiceModel/Bookings.cs"
5+
migration: "~/MyApp/Migrations/Migration1000.cs"
6+
tip: "Remove Bookings Feature: npx okai rm Bookings.d.ts",
7+
}
8+
9+
export enum RoomType {
10+
Single,
11+
Double,
12+
Queen,
13+
Twin,
14+
Suite,
15+
}
16+
17+
@Delete.validateHasRole("Manager")
18+
@tag("Bookings")
19+
@icon({svg:"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='currentColor' d='M16 10H8c-.55 0-1 .45-1 1s.45 1 1 1h8c.55 0 1-.45 1-1s-.45-1-1-1zm3-7h-1V2c0-.55-.45-1-1-1s-1 .45-1 1v1H8V2c0-.55-.45-1-1-1s-1 .45-1 1v1H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 16H6c-.55 0-1-.45-1-1V8h14v10c0 .55-.45 1-1 1zm-5-5H8c-.55 0-1 .45-1 1s.45 1 1 1h5c.55 0 1-.45 1-1s-.45-1-1-1z'/></svg>"})
20+
@notes("Captures a Persons Name & Room Booking information")
21+
@description("Booking Details")
22+
@validateHasRole("Employee")
23+
export class Booking extends AuditBase {
24+
@autoIncrement()
25+
id: number
26+
@Create.description("Name this Booking is for")
27+
@validateNotEmpty()
28+
name: string
29+
roomType: RoomType
30+
@validateGreaterThan(0)
31+
roomNumber: number
32+
@intlDateTime(DateStyle.Long)
33+
bookingStartDate: Date
34+
@intlRelativeTime()
35+
bookingEndDate?: Date
36+
@intlNumber({currency:"USD"})
37+
@validateGreaterThan(0)
38+
cost: decimal
39+
@input({type:"textarea"})
40+
notes?: string
41+
cancelled?: boolean
42+
@reference({selfId:"nameof(CreatedBy)",refId:"nameof(User.UserName)",refLabel:"nameof(User.DisplayName)"})
43+
employee: User
44+
}

MyApp.ServiceModel/User.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using ServiceStack;
2+
using ServiceStack.DataAnnotations;
3+
4+
namespace MyApp.ServiceModel;
5+
6+
/// <summary>
7+
/// Public User DTO
8+
/// </summary>
9+
[Icon(Svg = "<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path fill='currentColor' d='M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4'/></svg>")]
10+
[Alias("AspNetUsers")]
11+
public class User
12+
{
13+
public string Id { get; set; }
14+
public string UserName { get; set; }
15+
public string? FirstName { get; set; }
16+
public string? LastName { get; set; }
17+
public string? DisplayName { get; set; }
18+
public string? ProfileUrl { get; set; }
19+
}
20+
21+
[ValidateIsAdmin]
22+
public class QueryUsers : QueryDb<User>
23+
{
24+
public string? Id { get; set; }
25+
}

MyApp.ServiceModel/api.d.ts

Lines changed: 433 additions & 0 deletions
Large diffs are not rendered by default.

MyApp.Tests/MigrationTasks.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using NUnit.Framework;
2+
using ServiceStack.OrmLite;
3+
using MyApp.Migrations;
4+
using ServiceStack;
5+
using ServiceStack.Data;
6+
7+
namespace MyApp.Tests;
8+
9+
[TestFixture, Explicit, Category(nameof(MigrationTasks))]
10+
public class MigrationTasks
11+
{
12+
IDbConnectionFactory ResolveDbFactory() => new ConfigureDb().ConfigureAndResolve<IDbConnectionFactory>();
13+
Migrator CreateMigrator() => new(ResolveDbFactory(), typeof(Migration1000).Assembly);
14+
15+
[Test]
16+
public void Migrate()
17+
{
18+
var migrator = CreateMigrator();
19+
var result = migrator.Run();
20+
Assert.That(result.Succeeded);
21+
}
22+
23+
[Test]
24+
public void Revert_All()
25+
{
26+
var migrator = CreateMigrator();
27+
var result = migrator.Revert(Migrator.All);
28+
Assert.That(result.Succeeded);
29+
}
30+
31+
[Test]
32+
public void Revert_Last()
33+
{
34+
var migrator = CreateMigrator();
35+
var result = migrator.Revert(Migrator.Last);
36+
Assert.That(result.Succeeded);
37+
}
38+
39+
[Test]
40+
public void Rerun_Last_Migration()
41+
{
42+
Revert_Last();
43+
Migrate();
44+
}
45+
}

MyApp.slnx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Solution>
2+
<Project Path="MyApp.Client/MyApp.Client.esproj">
3+
<BuildType Project="Debug" />
4+
<Build />
5+
</Project>
6+
<Project Path="MyApp.ServiceInterface/MyApp.ServiceInterface.csproj" />
7+
<Project Path="MyApp.ServiceModel/MyApp.ServiceModel.csproj" />
8+
<Project Path="MyApp.Tests/MyApp.Tests.csproj" />
9+
<Project Path="MyApp/MyApp.csproj" />
10+
</Solution>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@page
2+
@model AccessDeniedModel
3+
@{
4+
ViewData["Title"] = "Access denied";
5+
}
6+
7+
<div class="mt-8 flex justify-center">
8+
<div>
9+
<h2 class="@Css.H2 text-danger">@ViewData["Title"]</h2>
10+
<p class="my-4 text-danger">You do not have access to this resource.</p>
11+
</div>
12+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
#nullable disable
4+
5+
using Microsoft.AspNetCore.Mvc.RazorPages;
6+
7+
namespace MyApp.Areas.Identity.Pages.Account
8+
{
9+
/// <summary>
10+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
11+
/// directly from your code. This API may change or be removed in future releases.
12+
/// </summary>
13+
public class AccessDeniedModel : PageModel
14+
{
15+
/// <summary>
16+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
17+
/// directly from your code. This API may change or be removed in future releases.
18+
/// </summary>
19+
public void OnGet()
20+
{
21+
}
22+
}
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@page
2+
@model ConfirmEmailModel
3+
@{
4+
ViewData["Title"] = "Confirm email";
5+
}
6+
7+
<div class="mt-8 flex justify-center">
8+
<div>
9+
<h2 class="@Css.H2">@ViewData["Title"]</h2>
10+
<p class="my-4">Thank you for confirming your email.</p>
11+
</div>
12+
</div>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
#nullable disable
4+
5+
using System;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Authorization;
10+
using Microsoft.AspNetCore.Identity;
11+
using Microsoft.AspNetCore.Mvc;
12+
using Microsoft.AspNetCore.Mvc.RazorPages;
13+
using Microsoft.AspNetCore.WebUtilities;
14+
using MyApp.Data;
15+
16+
namespace MyApp.Areas.Identity.Pages.Account
17+
{
18+
public class ConfirmEmailModel : PageModel
19+
{
20+
private readonly UserManager<ApplicationUser> _userManager;
21+
22+
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
23+
{
24+
_userManager = userManager;
25+
}
26+
27+
/// <summary>
28+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
29+
/// directly from your code. This API may change or be removed in future releases.
30+
/// </summary>
31+
[TempData]
32+
public string StatusMessage { get; set; }
33+
public async Task<IActionResult> OnGetAsync(string userId, string code)
34+
{
35+
if (userId == null || code == null)
36+
{
37+
return RedirectToPage("/Index");
38+
}
39+
40+
var user = await _userManager.FindByIdAsync(userId);
41+
if (user == null)
42+
{
43+
return NotFound($"Unable to load user with ID '{userId}'.");
44+
}
45+
46+
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
47+
var result = await _userManager.ConfirmEmailAsync(user, code);
48+
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
49+
return Page();
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)