Browse Source

Added identity service

pull/49/merge
Carlos Cañizares Estévez 8 years ago
parent
commit
f43ae5a84c
158 changed files with 44730 additions and 36 deletions
  1. +15
    -0
      build-images.ps1
  2. +18
    -6
      docker-compose.yml
  3. +4
    -2
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  4. +1
    -1
      src/Services/Basket/Basket.API/Model/IBasketRepository.cs
  5. +12
    -2
      src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs
  6. +7
    -0
      src/Services/Basket/Basket.API/Startup.cs
  7. +1
    -0
      src/Services/Basket/Basket.API/appsettings.json
  8. +17
    -0
      src/Services/Basket/Basket.API/docker-compose.yml
  9. +2
    -1
      src/Services/Basket/Basket.API/project.json
  10. +3
    -0
      src/Services/Identity/eShopOnContainers.Identity/.bowerrc
  11. +12
    -0
      src/Services/Identity/eShopOnContainers.Identity/Configuration/ClientCallBackUrls.cs
  12. +68
    -0
      src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs
  13. +333
    -0
      src/Services/Identity/eShopOnContainers.Identity/Controllers/AccountController.cs
  14. +142
    -0
      src/Services/Identity/eShopOnContainers.Identity/Controllers/ConsentController.cs
  15. +43
    -0
      src/Services/Identity/eShopOnContainers.Identity/Controllers/HomeController.cs
  16. +360
    -0
      src/Services/Identity/eShopOnContainers.Identity/Controllers/ManageController.cs
  17. +26
    -0
      src/Services/Identity/eShopOnContainers.Identity/Data/ApplicationDbContext.cs
  18. +1
    -1
      src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161019122215_Init_Scheme.Designer.cs
  19. +0
    -0
      src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161019122215_Init_Scheme.cs
  20. +1
    -1
      src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161020101725_extendProfile.Designer.cs
  21. +0
    -0
      src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161020101725_extendProfile.cs
  22. +1
    -2
      src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/ApplicationDbContextModelSnapshot.cs
  23. +7
    -0
      src/Services/Identity/eShopOnContainers.Identity/Dockerfile
  24. +15
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs
  25. +15
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ForgotPasswordViewModel.cs
  26. +22
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/LoginViewModel.cs
  27. +27
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/RegisterViewModel.cs
  28. +27
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ResetPasswordViewModel.cs
  29. +19
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/SendCodeViewModel.cs
  30. +25
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/VerifyCodeViewModel.cs
  31. +30
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ApplicationUser.cs
  32. +16
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ConsentInputModel.cs
  33. +57
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ConsentViewModel.cs
  34. +13
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ErrorViewModel.cs
  35. +13
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/LoggedOutViewModel.cs
  36. +18
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/LoginInputModel.cs
  37. +20
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/LoginViewModel.cs
  38. +11
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/LogoutViewModel.cs
  39. +16
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/AddPhoneNumberViewModel.cs
  40. +27
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ChangePasswordViewModel.cs
  41. +15
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ConfigureTwoFactorViewModel.cs
  42. +12
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/FactorViewModel.cs
  43. +21
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/IndexViewModel.cs
  44. +16
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ManageLoginsViewModel.cs
  45. +14
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/RemoveLoginViewModel.cs
  46. +22
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/SetPasswordViewModel.cs
  47. +19
    -0
      src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/VerifyPhoneNumberViewModel.cs
  48. +25
    -0
      src/Services/Identity/eShopOnContainers.Identity/Program.cs
  49. +28
    -0
      src/Services/Identity/eShopOnContainers.Identity/Properties/launchSettings.json
  50. +34
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/EFLoginService.cs
  51. +12
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/IEmailSender.cs
  52. +14
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/ILoginService.cs
  53. +12
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/ISmsSender.cs
  54. +25
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/MessageServices.cs
  55. +112
    -0
      src/Services/Identity/eShopOnContainers.Identity/Startup.cs
  56. +10
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ConfirmEmail.cshtml
  57. +35
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ExternalLoginConfirmation.cshtml
  58. +8
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ExternalLoginFailure.cshtml
  59. +31
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ForgotPassword.cshtml
  60. +8
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ForgotPasswordConfirmation.cshtml
  61. +8
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/Lockout.cshtml
  62. +21
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/LoggedOut.cshtml
  63. +83
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/Login.cshtml
  64. +21
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/Logout.cshtml
  65. +42
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/Register.cshtml
  66. +43
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ResetPassword.cshtml
  67. +8
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/ResetPasswordConfirmation.cshtml
  68. +21
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/SendCode.cshtml
  69. +38
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Account/VerifyCode.cshtml
  70. +82
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Consent/Index.cshtml
  71. +34
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Consent/_ScopeListItem.cshtml
  72. +7
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Home/About.cshtml
  73. +17
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Home/Contact.cshtml
  74. +30
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Home/Index.cshtml
  75. +27
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/AddPhoneNumber.cshtml
  76. +42
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/ChangePassword.cshtml
  77. +71
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/Index.cshtml
  78. +54
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/ManageLogins.cshtml
  79. +38
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/SetPassword.cshtml
  80. +30
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Manage/VerifyPhoneNumber.cshtml
  81. +34
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Shared/Error.cshtml
  82. +54
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_Layout.cshtml
  83. +26
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_LoginPartial.cshtml
  84. +14
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_ValidationScriptsPartial.cshtml
  85. +7
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_ValidationSummary.cshtml
  86. +1
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/_ViewImports.cshtml
  87. +3
    -0
      src/Services/Identity/eShopOnContainers.Identity/Views/_ViewStart.cshtml
  88. +17
    -0
      src/Services/Identity/eShopOnContainers.Identity/appsettings.json
  89. +10
    -0
      src/Services/Identity/eShopOnContainers.Identity/bower.json
  90. +24
    -0
      src/Services/Identity/eShopOnContainers.Identity/bundleconfig.json
  91. +25
    -0
      src/Services/Identity/eShopOnContainers.Identity/docker-compose.yml
  92. +23
    -0
      src/Services/Identity/eShopOnContainers.Identity/eShopOnContainers.Identity.xproj
  93. +105
    -0
      src/Services/Identity/eShopOnContainers.Identity/project.json
  94. +14
    -0
      src/Services/Identity/eShopOnContainers.Identity/web.config
  95. +6
    -0
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/_references.js
  96. +39
    -0
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.css
  97. +57
    -0
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.less
  98. +1
    -0
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.min.css
  99. BIN
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/favicon.ico
  100. BIN
      src/Services/Identity/eShopOnContainers.Identity/wwwroot/icon.jpg

+ 15
- 0
build-images.ps1 View File

@ -22,13 +22,26 @@ dotnet publish $webPathToJson -o $webPathToPub
$webSPAPathToJson = $scriptPath + "\src\Web\WebSPA\eShopOnContainers.WebSPA\project.json" $webSPAPathToJson = $scriptPath + "\src\Web\WebSPA\eShopOnContainers.WebSPA\project.json"
Write-Host "webSPAPathToJson is $webSPAPathToJson" -ForegroundColor Yellow Write-Host "webSPAPathToJson is $webSPAPathToJson" -ForegroundColor Yellow
$webSPAPathToPub = $scriptPath + "\pub\webSPA" $webSPAPathToPub = $scriptPath + "\pub\webSPA"
#$webSPAPathToNpmBat = $scriptPath + "\src\Web\WebSPA\eShopOnContainers.WebSPA\buildspa.bat"
Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow
Write-Host "Restore Dependencies just in case as it is needed to run dotnet publish" -ForegroundColor Blue Write-Host "Restore Dependencies just in case as it is needed to run dotnet publish" -ForegroundColor Blue
dotnet restore $webSPAPathToJson dotnet restore $webSPAPathToJson
dotnet build $webSPAPathToJson dotnet build $webSPAPathToJson
#Start-Process "cmd.exe" "/c " + $webSPAPathToNpmBat
dotnet publish $webSPAPathToJson -o $webSPAPathToPub dotnet publish $webSPAPathToJson -o $webSPAPathToPub
# *** identitySvc image ***
$identitySvcPathToJson = $scriptPath + "\src\Services\Identity\eShopOnContainers.Identity\project.json"
Write-Host "identitySvcPathToJson is $identitySvcPathToJson" -ForegroundColor Yellow
$identitySvcPathToPub = $scriptPath + "\pub\identity"
Write-Host "identitySvcPathToPub is $identitySvcPathToPub" -ForegroundColor Yellow
Write-Host "Restore Dependencies just in case as it is needed to run dotnet publish" -ForegroundColor Blue
dotnet restore $identitySvcPathToJson
dotnet build $identitySvcPathToJson
dotnet publish $identitySvcPathToJson -o $identitySvcPathToPub
#*** Catalog service image *** #*** Catalog service image ***
$catalogPathToJson = $scriptPath + "\src\Services\Catalog\Catalog.API\project.json" $catalogPathToJson = $scriptPath + "\src\Services\Catalog\Catalog.API\project.json"
@ -63,8 +76,10 @@ dotnet restore $basketPathToJson
dotnet build $basketPathToJson dotnet build $basketPathToJson
dotnet publish $basketPathToJson -o $basketPathToPub dotnet publish $basketPathToJson -o $basketPathToPub
#*** build docker images ***
docker build -t eshop/web $webPathToPub docker build -t eshop/web $webPathToPub
docker build -t eshop/catalog.api $catalogPathToPub docker build -t eshop/catalog.api $catalogPathToPub
docker build -t eshop/ordering.api $orderingPathToPub docker build -t eshop/ordering.api $orderingPathToPub
docker build -t eshop/basket.api $basketPathToPub docker build -t eshop/basket.api $basketPathToPub
docker build -t eshop/webspa $webSPAPathToPub docker build -t eshop/webspa $webSPAPathToPub
docker build -t eshop/identity $identitySvcPathToPub

+ 18
- 6
docker-compose.yml View File

@ -9,11 +9,13 @@ services:
environment: environment:
- CatalogUrl=http://catalog.api - CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api - OrderingUrl=http://ordering.api
- IdentityUrl=http://identity.service
- BasketUrl=http://basket.api
ports: ports:
- "5100:80" - "5100:80"
depends_on: depends_on:
- catalog.api - catalog.api
- identity.data
- identity.service
- basket.api - basket.api
webspa: webspa:
@ -24,12 +26,14 @@ services:
environment: environment:
- CatalogUrl=http://catalog.api - CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api - OrderingUrl=http://ordering.api
- IdentityUrl=http://identity.service
- BasketUrl=http://basket.api
ports: ports:
- "5104:80" - "5104:80"
depends_on: depends_on:
- catalog.api - catalog.api
- identity.data
- basket.api - basket.api
- identity.service
catalog.api: catalog.api:
image: eshop/catalog.api image: eshop/catalog.api
@ -56,10 +60,6 @@ services:
- ConnectionString=Server=ordering.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - ConnectionString=Server=ordering.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
ports: ports:
- "5102:80" - "5102:80"
# (Go to Production): For secured/final deployment, remove Ports mapping and
# leave just the internal expose section
# expose:
# - "80"
extra_hosts: extra_hosts:
- "CESARDLBOOKVHD:10.0.75.1" - "CESARDLBOOKVHD:10.0.75.1"
depends_on: depends_on:
@ -70,6 +70,17 @@ services:
ports: ports:
- "5432:1433" - "5432:1433"
identity.service:
image: eshop/identity
environment:
- Spa:http://webspa
expose:
- "80"
ports:
- "5105:80"
depends_on:
- identity.data
identity.data: identity.data:
image: microsoft/mssql-server-linux image: microsoft/mssql-server-linux
environment: environment:
@ -89,6 +100,7 @@ services:
- "5103:80" - "5103:80"
depends_on: depends_on:
- basket.data - basket.data
- identity.service
basket.data: basket.data:
image: redis image: redis

+ 4
- 2
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -31,9 +31,11 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
// POST api/values // POST api/values
[HttpPost] [HttpPost]
public void Post([FromBody]CustomerBasket value)
public async Task<IActionResult> Post([FromBody]CustomerBasket value)
{ {
_repository.UpdateBasket(value);
var basket = await _repository.UpdateBasket(value);
return Ok(basket);
} }
// DELETE api/values/5 // DELETE api/values/5


+ 1
- 1
src/Services/Basket/Basket.API/Model/IBasketRepository.cs View File

@ -8,7 +8,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
public interface IBasketRepository public interface IBasketRepository
{ {
Task<CustomerBasket> GetBasket(string customerId); Task<CustomerBasket> GetBasket(string customerId);
Task<bool> UpdateBasket(CustomerBasket basket);
Task<CustomerBasket> UpdateBasket(CustomerBasket basket);
Task<bool> DeleteBasket(string id); Task<bool> DeleteBasket(string id);
} }
} }

+ 12
- 2
src/Services/Basket/Basket.API/Model/RedisBasketRepository.cs View File

@ -44,10 +44,19 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
return JsonConvert.DeserializeObject<CustomerBasket>(data); return JsonConvert.DeserializeObject<CustomerBasket>(data);
} }
public async Task<bool> UpdateBasket(CustomerBasket basket)
public async Task<CustomerBasket> UpdateBasket(CustomerBasket basket)
{ {
var database = await GetDatabase(); var database = await GetDatabase();
return await database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket));
var created = await database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket));
if (!created)
{
_logger.LogInformation("Problem persisting the item");
return null;
}
_logger.LogInformation("basket item persisted succesfully");
return await GetBasket(basket.BuyerId);
} }
private async Task<IDatabase> GetDatabase() private async Task<IDatabase> GetDatabase()
@ -64,3 +73,4 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
} }
} }
} }

+ 7
- 0
src/Services/Basket/Basket.API/Startup.cs View File

@ -56,6 +56,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(); loggerFactory.AddDebug();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration.GetValue("IdentityUrl", "http://localhost:5000"),
ScopeName = "basket",
RequireHttpsMetadata = false
});
app.UseMvc(); app.UseMvc();
} }
} }


+ 1
- 0
src/Services/Basket/Basket.API/appsettings.json View File

@ -7,5 +7,6 @@
"Microsoft": "Information" "Microsoft": "Information"
} }
}, },
"identityUrl": "http://localhost:5105",
"ConnectionString": "127.0.0.1" "ConnectionString": "127.0.0.1"
} }

+ 17
- 0
src/Services/Basket/Basket.API/docker-compose.yml View File

@ -15,3 +15,20 @@ services:
basket.data: basket.data:
image: redis image: redis
identity.service:
image: eshop/identity
expose:
- "80"
ports:
- "5105:80"
depends_on:
- identity.data
identity.data:
image: microsoft/mssql-server-linux
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"

+ 2
- 1
src/Services/Basket/Basket.API/project.json View File

@ -16,7 +16,8 @@
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"StackExchange.Redis": "1.1.608", "StackExchange.Redis": "1.1.608",
"Newtonsoft.Json": "9.0.1"
"Newtonsoft.Json": "9.0.1",
"IdentityServer4.AccessTokenValidation": "1.0.1-rc3"
}, },
"tools": { "tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"


+ 3
- 0
src/Services/Identity/eShopOnContainers.Identity/.bowerrc View File

@ -0,0 +1,3 @@
{
"directory": "wwwroot/lib"
}

+ 12
- 0
src/Services/Identity/eShopOnContainers.Identity/Configuration/ClientCallBackUrls.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Configuration
{
public class ClientCallBackUrls
{
public string Spa { get; set; }
}
}

+ 68
- 0
src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs View File

@ -0,0 +1,68 @@
using IdentityServer4.Models;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
namespace eShopOnContainers.Identity.Configuration
{
public class Config
{
private readonly IOptions<ClientCallBackUrls> _settings;
public Config(IOptions<ClientCallBackUrls> settings)
{
_settings = settings;
}
// scopes define the resources in your system
public static IEnumerable<Scope> GetScopes()
{
return new List<Scope>
{
//Authentication OpenId uses this scopes;
StandardScopes.OpenId,
StandardScopes.Profile,
//Each api we want to securice;
new Scope
{
Name = "orders",
Description = "Orders Service"
},
new Scope
{
Name = "basket",
Description = "Basket Service"
}
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// JavaScript Client
new Client
{
ClientId = "js",
ClientName = "eShop SPA OpenId Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5003/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
AllowedCorsOrigins = { "http://localhost:5003" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
"orders",
"basket"
}
}
};
}
}
}

+ 333
- 0
src/Services/Identity/eShopOnContainers.Identity/Controllers/AccountController.cs View File

@ -0,0 +1,333 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityModel;
using IdentityServer4.Quickstart.UI.Models;
using IdentityServer4.Services;
using IdentityServer4.Services.InMemory;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using eShopOnContainers.Identity.Services;
using eShopOnContainers.Identity.Models;
using Microsoft.Extensions.Logging;
namespace IdentityServer4.Quickstart.UI.Controllers
{
/// <summary>
/// This sample controller implements a typical login/logout/provision workflow for local and external accounts.
/// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production!
/// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval
/// </summary>
public class AccountController : Controller
{
//private readonly InMemoryUserLoginService _loginService;
private readonly ILoginService<ApplicationUser> _loginService;
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clientStore;
private readonly ILogger _logger;
public AccountController(
//InMemoryUserLoginService loginService,
ILoginService<ApplicationUser> loginService,
IIdentityServerInteractionService interaction,
IClientStore clientStore,
ILoggerFactory loggerFactory)
{
_loginService = loginService;
_interaction = interaction;
_clientStore = clientStore;
_logger = loggerFactory.CreateLogger<AccountController>();
}
/// <summary>
/// Show login page
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
{
// if IdP is passed, then bypass showing the login screen
return ExternalLogin(context.IdP, returnUrl);
}
var vm = await BuildLoginViewModelAsync(returnUrl, context);
if (vm.EnableLocalLogin == false && vm.ExternalProviders.Count() == 1)
{
// only one option for logging in
return ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
}
return View(vm);
}
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
var user = await _loginService.FindByUsername(model.Username);
// validate username/password against in-memory store
if (await _loginService.ValidateCredentials(user, model.Password))
{
// issue authentication cookie with subject ID and username
//var user = _loginService.FindByUsername(model.Username);
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1)
};
};
//await HttpContext.Authentication.SignInAsync(, user.UserName, props);
await _loginService.SignIn(user);
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return Redirect("~/");
}
ModelState.AddModelError("", "Invalid username or password.");
}
// something went wrong, show form with error
var vm = await BuildLoginViewModelAsync(model);
return View(vm);
}
async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
{
var providers = HttpContext.Authentication.GetAuthenticationSchemes()
.Where(x => x.DisplayName != null)
.Select(x => new ExternalProvider
{
DisplayName = x.DisplayName,
AuthenticationScheme = x.AuthenticationScheme
});
var allowLocal = true;
if (context?.ClientId != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
if (client != null)
{
allowLocal = client.EnableLocalLogin;
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
{
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme));
}
}
}
return new LoginViewModel
{
EnableLocalLogin = allowLocal,
ReturnUrl = returnUrl,
Username = context?.LoginHint,
ExternalProviders = providers.ToArray()
};
}
async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
{
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context);
vm.Username = model.Username;
vm.RememberLogin = model.RememberLogin;
return vm;
}
/// <summary>
/// Show logout page
/// </summary>
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
if (User.Identity.IsAuthenticated == false)
{
// if the user is not authenticated, then just show logged out page
return await Logout(new LogoutViewModel { LogoutId = logoutId });
}
var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
// it's safe to automatically sign-out
return await Logout(new LogoutViewModel { LogoutId = logoutId });
}
// show the logout prompt. this prevents attacks where the user
// is automatically signed out by another malicious web page.
var vm = new LogoutViewModel
{
LogoutId = logoutId
};
return View(vm);
}
/// <summary>
/// Handle logout page postback
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
{
if (model.LogoutId == null)
{
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// before we signout and redirect away to the external IdP for signout
model.LogoutId = await _interaction.CreateLogoutContextAsync();
}
string url = "/Account/Logout?logoutId=" + model.LogoutId;
try
{
// hack: try/catch to handle social providers that throw
await HttpContext.Authentication.SignOutAsync(idp, new AuthenticationProperties { RedirectUri = url });
}
catch(NotSupportedException)
{
}
}
// delete authentication cookie
await HttpContext.Authentication.SignOutAsync();
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(model.LogoutId);
var vm = new LoggedOutViewModel
{
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
return View("LoggedOut", vm);
}
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public IActionResult ExternalLogin(string provider, string returnUrl)
{
if (returnUrl != null)
{
returnUrl = UrlEncoder.Default.Encode(returnUrl);
}
returnUrl = "/account/externallogincallback?returnUrl=" + returnUrl;
// start challenge and roundtrip the return URL
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", provider } }
};
return new ChallengeResult(provider, props);
}
/// <summary>
/// Post processing of external authentication
/// </summary>
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
{
// read external identity from the temporary cookie
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
//var tempUser = info?.Principal;
//if (tempUser == null)
//{
// throw new Exception("External authentication error");
//}
//// retrieve claims of the external user
//var claims = tempUser.Claims.ToList();
//// try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier
//// depending on the external provider, some other claim type might be used
//var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
//if (userIdClaim == null)
//{
// userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
//}
//if (userIdClaim == null)
//{
// throw new Exception("Unknown userid");
//}
//// remove the user id claim from the claims collection and move to the userId property
//// also set the name of the external authentication provider
//claims.Remove(userIdClaim);
//var provider = info.Properties.Items["scheme"];
//var userId = userIdClaim.Value;
//// check if the external user is already provisioned
//var user = _loginService.FindByExternalProvider(provider, userId);
//if (user == null)
//{
// // this sample simply auto-provisions new external user
// // another common approach is to start a registrations workflow first
// user = _loginService.AutoProvisionUser(provider, userId, claims);
//}
//var additionalClaims = new List<Claim>();
//// if the external system sent a session id claim, copy it over
//var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
//if (sid != null)
//{
// additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
//}
//// issue authentication cookie for user
//await HttpContext.Authentication.SignInAsync(user.Subject, user.Username, provider, additionalClaims.ToArray());
//// delete temporary cookie used during external authentication
//await HttpContext.Authentication.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
//// validate return URL and redirect back to authorization endpoint
//if (_interaction.IsValidReturnUrl(returnUrl))
//{
// return Redirect(returnUrl);
//}
return Redirect("~/");
}
}
}

+ 142
- 0
src/Services/Identity/eShopOnContainers.Identity/Controllers/ConsentController.cs View File

@ -0,0 +1,142 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using IdentityServer4.Quickstart.UI.Models;
namespace IdentityServer4.Quickstart.UI.Controllers
{
/// <summary>
/// This controller implements the consent logic
/// </summary>
public class ConsentController : Controller
{
private readonly ILogger<ConsentController> _logger;
private readonly IClientStore _clientStore;
private readonly IScopeStore _scopeStore;
private readonly IIdentityServerInteractionService _interaction;
public ConsentController(
ILogger<ConsentController> logger,
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IScopeStore scopeStore)
{
_logger = logger;
_interaction = interaction;
_clientStore = clientStore;
_scopeStore = scopeStore;
}
/// <summary>
/// Shows the consent screen
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Index(string returnUrl)
{
var vm = await BuildViewModelAsync(returnUrl);
if (vm != null)
{
return View("Index", vm);
}
return View("Error");
}
/// <summary>
/// Handles the consent screen postback
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ConsentInputModel model)
{
// parse the return URL back to an AuthorizeRequest object
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
ConsentResponse response = null;
// user clicked 'no' - send back the standard 'access_denied' response
if (model.Button == "no")
{
response = ConsentResponse.Denied;
}
// user clicked 'yes' - validate the data
else if (model.Button == "yes" && model != null)
{
// if the user consented to some scope, build the response model
if (model.ScopesConsented != null && model.ScopesConsented.Any())
{
response = new ConsentResponse
{
RememberConsent = model.RememberConsent,
ScopesConsented = model.ScopesConsented
};
}
else
{
ModelState.AddModelError("", "You must pick at least one permission.");
}
}
else
{
ModelState.AddModelError("", "Invalid Selection");
}
if (response != null)
{
// communicate outcome of consent back to identityserver
await _interaction.GrantConsentAsync(request, response);
// redirect back to authorization endpoint
return Redirect(model.ReturnUrl);
}
var vm = await BuildViewModelAsync(model.ReturnUrl, model);
if (vm != null)
{
return View("Index", vm);
}
return View("Error");
}
async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
{
var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (request != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
if (client != null)
{
var scopes = await _scopeStore.FindEnabledScopesAsync(request.ScopesRequested);
if (scopes != null && scopes.Any())
{
return new ConsentViewModel(model, returnUrl, request, client, scopes);
}
else
{
_logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y));
}
}
else
{
_logger.LogError("Invalid client id: {0}", request.ClientId);
}
}
else
{
_logger.LogError("No consent request matching request: {0}", returnUrl);
}
return null;
}
}
}

+ 43
- 0
src/Services/Identity/eShopOnContainers.Identity/Controllers/HomeController.cs View File

@ -0,0 +1,43 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Quickstart.UI.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace IdentityServer4.Quickstart.UI.Controllers
{
public class HomeController : Controller
{
private readonly IIdentityServerInteractionService _interaction;
public HomeController(IIdentityServerInteractionService interaction)
{
_interaction = interaction;
}
public IActionResult Index()
{
return View();
}
/// <summary>
/// Shows the error page
/// </summary>
public async Task<IActionResult> Error(string errorId)
{
var vm = new ErrorViewModel();
// retrieve error details from identityserver
var message = await _interaction.GetErrorContextAsync(errorId);
if (message != null)
{
vm.Error = message;
}
return View("Error", vm);
}
}
}

+ 360
- 0
src/Services/Identity/eShopOnContainers.Identity/Controllers/ManageController.cs View File

@ -0,0 +1,360 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using eShopOnContainers.Identity.Models;
using eShopOnContainers.Identity.Models.ManageViewModels;
using eShopOnContainers.Identity.Services;
namespace eShopOnContainers.Identity.Controllers
{
[Authorize]
public class ManageController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;
public ManageController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<ManageController>();
}
//
// GET: /Manage/Index
[HttpGet]
public async Task<IActionResult> Index(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set."
: message == ManageMessageId.Error ? "An error has occurred."
: message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added."
: message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
: "";
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var model = new IndexViewModel
{
HasPassword = await _userManager.HasPasswordAsync(user),
PhoneNumber = await _userManager.GetPhoneNumberAsync(user),
TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user),
Logins = await _userManager.GetLoginsAsync(user),
BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user)
};
return View(model);
}
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}
//
// GET: /Manage/AddPhoneNumber
public IActionResult AddPhoneNumber()
{
return View();
}
//
// POST: /Manage/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Generate the token and send it
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber);
await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code);
return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber });
}
//
// POST: /Manage/EnableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EnableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await _userManager.SetTwoFactorEnabledAsync(user, true);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(1, "User enabled two-factor authentication.");
}
return RedirectToAction(nameof(Index), "Manage");
}
//
// POST: /Manage/DisableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DisableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(2, "User disabled two-factor authentication.");
}
return RedirectToAction(nameof(Index), "Manage");
}
//
// GET: /Manage/VerifyPhoneNumber
[HttpGet]
public async Task<IActionResult> VerifyPhoneNumber(string phoneNumber)
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, phoneNumber);
// Send an SMS to verify the phone number
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
//
// POST: /Manage/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay the form
ModelState.AddModelError(string.Empty, "Failed to verify phone number");
return View(model);
}
//
// POST: /Manage/RemovePhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemovePhoneNumber()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.SetPhoneNumberAsync(user, null);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess });
}
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/ChangePassword
[HttpGet]
public IActionResult ChangePassword()
{
return View();
}
//
// POST: /Manage/ChangePassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User changed their password successfully.");
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/SetPassword
[HttpGet]
public IActionResult SetPassword()
{
return View();
}
//
// POST: /Manage/SetPassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.AddPasswordAsync(user, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//GET: /Manage/ManageLogins
[HttpGet]
public async Task<IActionResult> ManageLogins(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: message == ManageMessageId.AddLoginSuccess ? "The external login was added."
: message == ManageMessageId.Error ? "An error has occurred."
: "";
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var userLogins = await _userManager.GetLoginsAsync(user);
var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();
ViewData["ShowRemoveButton"] = user.PasswordHash != null || userLogins.Count > 1;
return View(new ManageLoginsViewModel
{
CurrentLogins = userLogins,
OtherLogins = otherLogins
});
}
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action("LinkLoginCallback", "Manage");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return Challenge(properties, provider);
}
//
// GET: /Manage/LinkLoginCallback
[HttpGet]
public async Task<ActionResult> LinkLoginCallback()
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user));
if (info == null)
{
return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error });
}
var result = await _userManager.AddLoginAsync(user, info);
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}
#region Helpers
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
public enum ManageMessageId
{
AddPhoneSuccess,
AddLoginSuccess,
ChangePasswordSuccess,
SetTwoFactorSuccess,
SetPasswordSuccess,
RemoveLoginSuccess,
RemovePhoneSuccess,
Error
}
private Task<ApplicationUser> GetCurrentUserAsync()
{
return _userManager.GetUserAsync(HttpContext.User);
}
#endregion
}
}

+ 26
- 0
src/Services/Identity/eShopOnContainers.Identity/Data/ApplicationDbContext.cs View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using eShopOnContainers.Identity.Models;
namespace eShopOnContainers.Identity.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}

src/Web/WebMVC/Migrations/20161019122215_Init_Scheme.Designer.cs → src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161019122215_Init_Scheme.Designer.cs View File

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.WebMVC.Data;
using eShopOnContainers.Identity.Data;
namespace WebMVC.Migrations namespace WebMVC.Migrations
{ {

src/Web/WebMVC/Migrations/20161019122215_Init_Scheme.cs → src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161019122215_Init_Scheme.cs View File


src/Web/WebMVC/Migrations/20161020101725_extendProfile.Designer.cs → src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161020101725_extendProfile.Designer.cs View File

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.WebMVC.Data;
using eShopOnContainers.Identity.Data;
namespace WebMVC.Migrations namespace WebMVC.Migrations
{ {

src/Web/WebMVC/Migrations/20161020101725_extendProfile.cs → src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/20161020101725_extendProfile.cs View File


src/Web/WebMVC/Migrations/ApplicationDbContextModelSnapshot.cs → src/Services/Identity/eShopOnContainers.Identity/Data/Migrations/ApplicationDbContextModelSnapshot.cs View File

@ -2,8 +2,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.WebMVC.Data;
using eShopOnContainers.Identity.Data;
namespace WebMVC.Migrations namespace WebMVC.Migrations
{ {

+ 7
- 0
src/Services/Identity/eShopOnContainers.Identity/Dockerfile View File

@ -0,0 +1,7 @@
FROM microsoft/aspnetcore:1.0.1
ENTRYPOINT ["dotnet", "eShopOnContainers.Identity.dll"]
ARG source=.
WORKDIR /app
ENV ASPNETCORE_URLS http://*:80
EXPOSE 80
COPY $source .

+ 15
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class ExternalLoginConfirmationViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}

+ 15
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ForgotPasswordViewModel.cs View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class ForgotPasswordViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
}

+ 22
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/LoginViewModel.cs View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}

+ 27
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/RegisterViewModel.cs View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
}

+ 27
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/ResetPasswordViewModel.cs View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class ResetPasswordViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string Code { get; set; }
}
}

+ 19
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/SendCodeViewModel.cs View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class SendCodeViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
public string ReturnUrl { get; set; }
public bool RememberMe { get; set; }
}
}

+ 25
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/AccountViewModels/VerifyCodeViewModel.cs View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.AccountViewModels
{
public class VerifyCodeViewModel
{
[Required]
public string Provider { get; set; }
[Required]
public string Code { get; set; }
public string ReturnUrl { get; set; }
[Display(Name = "Remember this browser?")]
public bool RememberBrowser { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}

+ 30
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ApplicationUser.cs View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace eShopOnContainers.Identity.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string CardNumber { get; set; }
public string SecurityNumber { get; set; }
public string Expiration { get; set; }
public string CardHolderName { get; set; }
public int CardType { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string StateCode { get; set; }
public string Country { get; set; }
public string CountryCode { get; set; }
public string ZipCode { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
}

+ 16
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ConsentInputModel.cs View File

@ -0,0 +1,16 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using System.Collections.Generic;
namespace IdentityServer4.Quickstart.UI.Models
{
public class ConsentInputModel
{
public string Button { get; set; }
public IEnumerable<string> ScopesConsented { get; set; }
public bool RememberConsent { get; set; }
public string ReturnUrl { get; set; }
}
}

+ 57
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ConsentViewModel.cs View File

@ -0,0 +1,57 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using IdentityServer4.Models;
namespace IdentityServer4.Quickstart.UI.Models
{
public class ConsentViewModel : ConsentInputModel
{
public ConsentViewModel(ConsentInputModel model, string returnUrl, AuthorizationRequest request, Client client, IEnumerable<Scope> scopes)
{
RememberConsent = model?.RememberConsent ?? true;
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
ReturnUrl = returnUrl;
ClientName = client.ClientName;
ClientUrl = client.ClientUri;
ClientLogoUrl = client.LogoUri;
AllowRememberConsent = client.AllowRememberConsent;
IdentityScopes = scopes.Where(x => x.Type == ScopeType.Identity).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
ResourceScopes = scopes.Where(x => x.Type == ScopeType.Resource).Select(x => new ScopeViewModel(x, ScopesConsented.Contains(x.Name) || model == null)).ToArray();
}
public string ClientName { get; set; }
public string ClientUrl { get; set; }
public string ClientLogoUrl { get; set; }
public bool AllowRememberConsent { get; set; }
public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
}
public class ScopeViewModel
{
public ScopeViewModel(Scope scope, bool check)
{
Name = scope.Name;
DisplayName = scope.DisplayName;
Description = scope.Description;
Emphasize = scope.Emphasize;
Required = scope.Required;
Checked = check || scope.Required;
}
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public bool Emphasize { get; set; }
public bool Required { get; set; }
public bool Checked { get; set; }
}
}

+ 13
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ErrorViewModel.cs View File

@ -0,0 +1,13 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityServer4.Models;
namespace IdentityServer4.Quickstart.UI.Models
{
public class ErrorViewModel
{
public ErrorMessage Error { get; set; }
}
}

+ 13
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/LoggedOutViewModel.cs View File

@ -0,0 +1,13 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
namespace IdentityServer4.Quickstart.UI.Models
{
public class LoggedOutViewModel
{
public string PostLogoutRedirectUri { get; set; }
public string ClientName { get; set; }
public string SignOutIframeUrl { get; set; }
}
}

+ 18
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/LoginInputModel.cs View File

@ -0,0 +1,18 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using System.ComponentModel.DataAnnotations;
namespace IdentityServer4.Quickstart.UI.Models
{
public class LoginInputModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
public bool RememberLogin { get; set; }
public string ReturnUrl { get; set; }
}
}

+ 20
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/LoginViewModel.cs View File

@ -0,0 +1,20 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using System.Collections.Generic;
namespace IdentityServer4.Quickstart.UI.Models
{
public class LoginViewModel : LoginInputModel
{
public bool EnableLocalLogin { get; set; }
public IEnumerable<ExternalProvider> ExternalProviders { get; set; }
}
public class ExternalProvider
{
public string DisplayName { get; set; }
public string AuthenticationScheme { get; set; }
}
}

+ 11
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/LogoutViewModel.cs View File

@ -0,0 +1,11 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
namespace IdentityServer4.Quickstart.UI.Models
{
public class LogoutViewModel
{
public string LogoutId { get; set; }
}
}

+ 16
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/AddPhoneNumberViewModel.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class AddPhoneNumberViewModel
{
[Required]
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
}

+ 27
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ChangePasswordViewModel.cs View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class ChangePasswordViewModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
}

+ 15
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ConfigureTwoFactorViewModel.cs View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class ConfigureTwoFactorViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
}
}

+ 12
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/FactorViewModel.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class FactorViewModel
{
public string Purpose { get; set; }
}
}

+ 21
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/IndexViewModel.cs View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class IndexViewModel
{
public bool HasPassword { get; set; }
public IList<UserLoginInfo> Logins { get; set; }
public string PhoneNumber { get; set; }
public bool TwoFactor { get; set; }
public bool BrowserRemembered { get; set; }
}
}

+ 16
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/ManageLoginsViewModel.cs View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationDescription> OtherLogins { get; set; }
}
}

+ 14
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/RemoveLoginViewModel.cs View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class RemoveLoginViewModel
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
}
}

+ 22
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/SetPasswordViewModel.cs View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class SetPasswordViewModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
}

+ 19
- 0
src/Services/Identity/eShopOnContainers.Identity/Models/ManageViewModels/VerifyPhoneNumberViewModel.cs View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Models.ManageViewModels
{
public class VerifyPhoneNumberViewModel
{
[Required]
public string Code { get; set; }
[Required]
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
}

+ 25
- 0
src/Services/Identity/eShopOnContainers.Identity/Program.cs View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace eShopOnContainers.Identity
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
//.UseUrls("http://localhost:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

+ 28
- 0
src/Services/Identity/eShopOnContainers.Identity/Properties/launchSettings.json View File

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5000",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"eShopOnContainers.Identity": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

+ 34
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/EFLoginService.cs View File

@ -0,0 +1,34 @@
using eShopOnContainers.Identity.Models;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Services
{
public class EFLoginService : ILoginService<ApplicationUser>
{
UserManager<ApplicationUser> _userManager;
SignInManager<ApplicationUser> _signInManager;
public EFLoginService(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) {
_userManager = userManager;
_signInManager = signInManager;
}
public async Task<ApplicationUser> FindByUsername(string user)
{
return await _userManager.FindByNameAsync(user);
}
public async Task<bool> ValidateCredentials(ApplicationUser user, string password)
{
return await _userManager.CheckPasswordAsync(user, password);
}
public Task SignIn(ApplicationUser user) {
return _signInManager.SignInAsync(user, true);
}
}
}

+ 12
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/IEmailSender.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Services
{
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
}

+ 14
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/ILoginService.cs View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Services
{
public interface ILoginService<T>
{
Task<bool> ValidateCredentials(T user, string password);
Task<T> FindByUsername(string user);
Task SignIn(T user);
}
}

+ 12
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/ISmsSender.cs View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Services
{
public interface ISmsSender
{
Task SendSmsAsync(string number, string message);
}
}

+ 25
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/MessageServices.cs View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace eShopOnContainers.Identity.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link http://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public Task SendEmailAsync(string email, string subject, string message)
{
// Plug in your email service here to send an email.
return Task.FromResult(0);
}
public Task SendSmsAsync(string number, string message)
{
// Plug in your SMS service here to send a text message.
return Task.FromResult(0);
}
}
}

+ 112
- 0
src/Services/Identity/eShopOnContainers.Identity/Startup.cs View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using eShopOnContainers.Identity.Data;
using eShopOnContainers.Identity.Models;
using eShopOnContainers.Identity.Services;
using eShopOnContainers.Identity.Configuration;
namespace eShopOnContainers.Identity
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
// Adds IdentityServer
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryScopes(Config.GetScopes())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
//Configuration Settings:
services.AddOptions();
services.Configure<ClientCallBackUrls>(Configuration.GetSection("ClientCallBackUrls"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
// Adds IdentityServer
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
try
{
var context = (ApplicationDbContext)app
.ApplicationServices.GetService(typeof(ApplicationDbContext));
using (context)
{
context.Database.Migrate();
}
}
catch (Exception) { }
}
}
}

+ 10
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ConfirmEmail.cshtml View File

@ -0,0 +1,10 @@
@{
ViewData["Title"] = "Confirm Email";
}
<h2>@ViewData["Title"].</h2>
<div>
<p>
Thank you for confirming your email. Please <a asp-controller="Account" asp-action="Login">Click here to Log in</a>.
</p>
</div>

+ 35
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ExternalLoginConfirmation.cshtml View File

@ -0,0 +1,35 @@
@model ExternalLoginConfirmationViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"].</h2>
<h3>Associate your @ViewData["LoginProvider"] account.</h3>
<form asp-controller="Account" asp-action="ExternalLoginConfirmation" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Association Form</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<p class="text-info">
You've successfully authenticated with <strong>@ViewData["LoginProvider"]</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Register</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 8
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ExternalLoginFailure.cshtml View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Login Failure";
}
<header>
<h2>@ViewData["Title"].</h2>
<p class="text-danger">Unsuccessful login with service.</p>
</header>

+ 31
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ForgotPassword.cshtml View File

@ -0,0 +1,31 @@
@model ForgotPasswordViewModel
@{
ViewData["Title"] = "Forgot your password?";
}
<h2>@ViewData["Title"]</h2>
<p>
For more information on how to enable reset password please see this <a href="http://go.microsoft.com/fwlink/?LinkID=532713">article</a>.
</p>
@*<form asp-controller="Account" asp-action="ForgotPassword" method="post" class="form-horizontal">
<h4>Enter your email.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>*@
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 8
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ForgotPasswordConfirmation.cshtml View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Forgot Password Confirmation";
}
<h2>@ViewData["Title"].</h2>
<p>
Please check your email to reset your password.
</p>

+ 8
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/Lockout.cshtml View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Locked out";
}
<header>
<h1 class="text-danger">Locked out.</h1>
<p class="text-danger">This account has been locked out, please try again later.</p>
</header>

+ 21
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/LoggedOut.cshtml View File

@ -0,0 +1,21 @@
@model IdentityServer4.Quickstart.UI.Models.LoggedOutViewModel
<div class="page-header">
<h1>
Logout
<small>You are now logged out</small>
</h1>
@if (Model.PostLogoutRedirectUri != null)
{
<div>
Click <a href="@Model.PostLogoutRedirectUri">here</a> to return to the
<span>@Model.ClientName</span> application.
</div>
}
@if (Model.SignOutIframeUrl != null)
{
<iframe style="display:none" width="0" height="0" class="signout" src="@Model.SignOutIframeUrl"></iframe>
}
</div>

+ 83
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/Login.cshtml View File

@ -0,0 +1,83 @@
@model IdentityServer4.Quickstart.UI.Models.LoginViewModel
<div class="login-page">
<div class="page-header">
<h1>Login</h1>
</div>
@Html.Partial("_ValidationSummary")
<div class="row">
@if (Model.EnableLocalLogin)
{
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Local Login</h3>
</div>
<div class="panel-body">
<form asp-route="Login">
<input type="hidden" asp-for="ReturnUrl" />
<fieldset>
<div class="form-group">
<label asp-for="Username"></label>
<input class="form-control" placeholder="Username" asp-for="Username" autofocus>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input type="password" class="form-control" placeholder="Password" asp-for="Password" autocomplete="off">
</div>
<div class="form-group login-remember">
<label asp-for="RememberLogin">
<input asp-for="RememberLogin">
<strong>Remember My Login</strong>
</label>
</div>
<div class="form-group">
<button class="btn btn-primary">Login</button>
</div>
</fieldset>
</form>
</div>
</div>
</div>
}
@if (Model.ExternalProviders.Any())
{
<div class="col-md-6 col-sm-6 external-providers">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">External Login</h3>
</div>
<div class="panel-body">
<ul class="list-inline">
@foreach (var provider in Model.ExternalProviders)
{
<li>
<a class="btn btn-default"
asp-action="ExternalLogin"
asp-route-provider="@provider.AuthenticationScheme"
asp-route-returnUrl="@Model.ReturnUrl">
@provider.DisplayName
</a>
</li>
}
</ul>
</div>
</div>
</div>
}
@if (!Model.EnableLocalLogin && !Model.ExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>Invalid login request</strong>
There are no login schemes configured for this client.
</div>
}
</div>
</div>

+ 21
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/Logout.cshtml View File

@ -0,0 +1,21 @@
@model IdentityServer4.Quickstart.UI.Models.LogoutViewModel
<div class="logout-page">
<div class="page-header">
<h1>Logout</h1>
</div>
<div class="row">
<div class="col-sm-6">
<p>Would you like to logout of IdentityServer?</p>
<form asp-action="Logout">
<input type="hidden" name="logoutId" value="@Model.LogoutId" />
<fieldset>
<div class="form-group">
<button class="btn btn-primary">Yes</button>
</div>
</fieldset>
</form>
</div>
</div>
</div>

+ 42
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/Register.cshtml View File

@ -0,0 +1,42 @@
@model RegisterViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Register</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 43
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ResetPassword.cshtml View File

@ -0,0 +1,43 @@
@model ResetPasswordViewModel
@{
ViewData["Title"] = "Reset password";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="ResetPassword" method="post" class="form-horizontal">
<h4>Reset your password.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Code" type="hidden" />
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Reset</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 8
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/ResetPasswordConfirmation.cshtml View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Reset password confirmation";
}
<h1>@ViewData["Title"].</h1>
<p>
Your password has been reset. Please <a asp-controller="Account" asp-action="Login">Click here to log in</a>.
</p>

+ 21
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/SendCode.cshtml View File

@ -0,0 +1,21 @@
@model SendCodeViewModel
@{
ViewData["Title"] = "Send Verification Code";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="SendCode" asp-route-returnurl="@Model.ReturnUrl" method="post" class="form-horizontal">
<input asp-for="RememberMe" type="hidden" />
<div class="row">
<div class="col-md-8">
Select Two-Factor Authentication Provider:
<select asp-for="SelectedProvider" asp-items="Model.Providers"></select>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 38
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Account/VerifyCode.cshtml View File

@ -0,0 +1,38 @@
@model VerifyCodeViewModel
@{
ViewData["Title"] = "Verify";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Account" asp-action="VerifyCode" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Provider" type="hidden" />
<input asp-for="RememberMe" type="hidden" />
<h4>@ViewData["Status"]</h4>
<hr />
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<input asp-for="RememberBrowser" />
<label asp-for="RememberBrowser"></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 82
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Consent/Index.cshtml View File

@ -0,0 +1,82 @@
@model IdentityServer4.Quickstart.UI.Models.ConsentViewModel
<div class="page-consent">
<div class="row page-header">
<div class="col-sm-10">
@if (Model.ClientLogoUrl != null)
{
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
}
<h1>
@Model.ClientName
<small>is requesting your permission</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-sm-8">
@Html.Partial("_ValidationSummary")
<form asp-action="Index" class="consent-form">
<input type="hidden" asp-for="ReturnUrl" />
<div>Uncheck the permissions you do not wish to grant.</div>
@if (Model.IdentityScopes.Any())
{
<div class="panel panel-default consent-buttons">
<div class="panel-heading">
<span class="glyphicon glyphicon-user"></span>
Personal Information
</div>
<ul class="list-group">
@foreach (var scope in Model.IdentityScopes)
{
@Html.Partial("_ScopeListItem", scope)
}
</ul>
</div>
}
@if (Model.ResourceScopes.Any())
{
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-tasks"></span>
Application Access
</div>
<ul class="list-group">
@foreach (var scope in Model.ResourceScopes)
{
@Html.Partial("_ScopeListItem", scope)
}
</ul>
</div>
}
@if (Model.AllowRememberConsent)
{
<div class="consent-remember">
<label>
<input class="consent-scopecheck" asp-for="RememberConsent" />
<strong>Remember My Decision</strong>
</label>
</div>
}
<div class="consent-buttons">
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
<button name="button" value="no" class="btn">No, Do Not Allow</button>
@if (Model.ClientUrl != null)
{
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
<span class="glyphicon glyphicon-info-sign"></span>
<strong>@Model.ClientName</strong>
</a>
}
</div>
</form>
</div>
</div>
</div>

+ 34
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Consent/_ScopeListItem.cshtml View File

@ -0,0 +1,34 @@
@model IdentityServer4.Quickstart.UI.Models.ScopeViewModel
<li class="list-group-item">
<label>
<input class="consent-scopecheck"
type="checkbox"
name="ScopesConsented"
id="scopes_@Model.Name"
value="@Model.Name"
checked="@Model.Checked"
disabled="@Model.Required" />
@if (Model.Required)
{
<input type="hidden"
name="ScopesConsented"
value="@Model.Name" />
}
<strong>@Model.DisplayName</strong>
@if (Model.Emphasize)
{
<span class="glyphicon glyphicon-exclamation-sign"></span>
}
</label>
@if (Model.Required)
{
<span><em>(required)</em></span>
}
@if (Model.Description != null)
{
<div class="consent-description">
<label for="scopes_@Model.Name">@Model.Description</label>
</div>
}
</li>

+ 7
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Home/About.cshtml View File

@ -0,0 +1,7 @@
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>

+ 17
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Home/Contact.cshtml View File

@ -0,0 +1,17 @@
@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

+ 30
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Home/Index.cshtml View File

@ -0,0 +1,30 @@
<div class="welcome-page">
<div class="row page-header">
<div class="col-sm-10">
<h1>
<img class="icon" src="~/icon.jpg">
Welcome to IdentityServer4
@*<small>(build {version})</small>*@
</h1>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<p>
IdentityServer publishes a
<a href="~/.well-known/openid-configuration">discovery document</a>
where you can find metadata and links to all the endpoints, key material, etc.
</p>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<p>
Here are links to the
<a href="https://github.com/identityserver/IdentityServer4">source code repository</a>,
and <a href="https://github.com/identityserver/IdentityServer4.Samples">ready to use samples</a>.
</p>
</div>
</div>
</div>

+ 27
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/AddPhoneNumber.cshtml View File

@ -0,0 +1,27 @@
@model AddPhoneNumberViewModel
@{
ViewData["Title"] = "Add Phone Number";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="AddPhoneNumber" method="post" class="form-horizontal">
<h4>Add a phone number.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="PhoneNumber" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Send verification code</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 42
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/ChangePassword.cshtml View File

@ -0,0 +1,42 @@
@model ChangePasswordViewModel
@{
ViewData["Title"] = "Change Password";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal">
<h4>Change Password Form</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 71
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/Index.cshtml View File

@ -0,0 +1,71 @@
@model IndexViewModel
@{
ViewData["Title"] = "Manage your account";
}
<h2>@ViewData["Title"].</h2>
<p class="text-success">@ViewData["StatusMessage"]</p>
<div>
<h4>Change your account settings</h4>
<hr />
<dl class="dl-horizontal">
<dt>Password:</dt>
<dd>
@if (Model.HasPassword)
{
<a asp-controller="Manage" asp-action="ChangePassword" class="btn-bracketed">Change</a>
}
else
{
<a asp-controller="Manage" asp-action="SetPassword" class="btn-bracketed">Create</a>
}
</dd>
<dt>External Logins:</dt>
<dd>
@Model.Logins.Count <a asp-controller="Manage" asp-action="ManageLogins" class="btn-bracketed">Manage</a>
</dd>
<dt>Phone Number:</dt>
<dd>
<p>
Phone Numbers can used as a second factor of verification in two-factor authentication.
See <a href="http://go.microsoft.com/fwlink/?LinkID=532713">this article</a>
for details on setting up this ASP.NET application to support two-factor authentication using SMS.
</p>
@*@(Model.PhoneNumber ?? "None")
@if (Model.PhoneNumber != null)
{
<br />
<a asp-controller="Manage" asp-action="AddPhoneNumber" class="btn-bracketed">Change</a>
<form asp-controller="Manage" asp-action="RemovePhoneNumber" method="post">
[<button type="submit" class="btn-link">Remove</button>]
</form>
}
else
{
<a asp-controller="Manage" asp-action="AddPhoneNumber" class="btn-bracketed">Add</a>
}*@
</dd>
<dt>Two-Factor Authentication:</dt>
<dd>
<p>
There are no two-factor authentication providers configured. See <a href="http://go.microsoft.com/fwlink/?LinkID=532713">this article</a>
for setting up this application to support two-factor authentication.
</p>
@*@if (Model.TwoFactor)
{
<form asp-controller="Manage" asp-action="DisableTwoFactorAuthentication" method="post" class="form-horizontal">
Enabled <button type="submit" class="btn-link btn-bracketed">Disable</button>
</form>
}
else
{
<form asp-controller="Manage" asp-action="EnableTwoFactorAuthentication" method="post" class="form-horizontal">
<button type="submit" class="btn-link btn-bracketed">Enable</button> Disabled
</form>
}*@
</dd>
</dl>
</div>

+ 54
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/ManageLogins.cshtml View File

@ -0,0 +1,54 @@
@model ManageLoginsViewModel
@using Microsoft.AspNetCore.Http.Authentication
@{
ViewData["Title"] = "Manage your external logins";
}
<h2>@ViewData["Title"].</h2>
<p class="text-success">@ViewData["StatusMessage"]</p>
@if (Model.CurrentLogins.Count > 0)
{
<h4>Registered Logins</h4>
<table class="table">
<tbody>
@for (var index = 0; index < Model.CurrentLogins.Count; index++)
{
<tr>
<td>@Model.CurrentLogins[index].LoginProvider</td>
<td>
@if ((bool)ViewData["ShowRemoveButton"])
{
<form asp-controller="Manage" asp-action="RemoveLogin" method="post" class="form-horizontal">
<div>
<input asp-for="@Model.CurrentLogins[index].LoginProvider" name="LoginProvider" type="hidden" />
<input asp-for="@Model.CurrentLogins[index].ProviderKey" name="ProviderKey" type="hidden" />
<input type="submit" class="btn btn-default" value="Remove" title="Remove this @Model.CurrentLogins[index].LoginProvider login from your account" />
</div>
</form>
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
<form asp-controller="Manage" asp-action="LinkLogin" method="post" class="form-horizontal">
<div id="socialLoginList">
<p>
@foreach (var provider in Model.OtherLogins)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
}
</p>
</div>
</form>
}

+ 38
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/SetPassword.cshtml View File

@ -0,0 +1,38 @@
@model SetPasswordViewModel
@{
ViewData["Title"] = "Set Password";
}
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<form asp-controller="Manage" asp-action="SetPassword" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>Set your password</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Set password</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 30
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Manage/VerifyPhoneNumber.cshtml View File

@ -0,0 +1,30 @@
@model VerifyPhoneNumberViewModel
@{
ViewData["Title"] = "Verify Phone Number";
}
<h2>@ViewData["Title"].</h2>
<form asp-controller="Manage" asp-action="VerifyPhoneNumber" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<input asp-for="PhoneNumber" type="hidden" />
<h4>Add a phone number.</h4>
<h5>@ViewData["Status"]</h5>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

+ 34
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Shared/Error.cshtml View File

@ -0,0 +1,34 @@
@model IdentityServer4.Quickstart.UI.Models.ErrorViewModel
@{
var error = Model?.Error?.Error;
var request_id = Model?.Error?.RequestId;
}
<div class="error-page">
<div class="page-header">
<h1>Error</h1>
</div>
<div class="row">
<div class="col-sm-6">
<div class="alert alert-danger">
Sorry, there was an error
@if (error != null)
{
<strong>
<em>
: @error
</em>
</strong>
}
</div>
@if (request_id != null)
{
<div class="request-id">Request Id: @request_id</div>
}
</div>
</div>
</div>

+ 54
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_Layout.cshtml View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IdentityServer4</title>
<link rel="icon" type="image/x-icon" href="~/favicon.ico" />
<link rel="shortcut icon" type="image/x-icon" href="~/favicon.ico" />
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="~/">
<span class="navbar-brand">
<img src="~/icon.png" class="icon-banner">
IdentityServer4
</span>
</a>
</div>
@if (User.Identity.IsAuthenticated)
{
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">@User.Identity.Name <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a asp-action="Logout" asp-controller="Account">Logout</a></li>
@*<li class="divider"></li>
<li><a asp-route="Login" asp-route-id="@ViewContext.HttpContext.Request.Query["id"]">Login With Different Account</a></li>*@
</ul>
</li>
</ul>
}
</div>
</div>
<div class="container body-content">
@RenderBody()
</div>
<script src="~/lib/jquery/jquery.js"></script>
<script src="~/lib/bootstrap/js/bootstrap.js"></script>
@RenderSection("scripts", required: false)
</body>
</html>

+ 26
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_LoginPartial.cshtml View File

@ -0,0 +1,26 @@
@using Microsoft.AspNetCore.Identity
@using eShopOnContainers.Identity.Models
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="LogOff" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}

+ 14
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_ValidationScriptsPartial.cshtml View File

@ -0,0 +1,14 @@
<environment names="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
</script>
</environment>

+ 7
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/Shared/_ValidationSummary.cshtml View File

@ -0,0 +1,7 @@
@if (ViewContext.ModelState.IsValid == false)
{
<div class="alert alert-danger">
<strong>Error</strong>
<div asp-validation-summary="All" class="danger"></div>
</div>
}

+ 1
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/_ViewImports.cshtml View File

@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

+ 3
- 0
src/Services/Identity/eShopOnContainers.Identity/Views/_ViewStart.cshtml View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

+ 17
- 0
src/Services/Identity/eShopOnContainers.Identity/appsettings.json View File

@ -0,0 +1,17 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=identity.data;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word"
//"DefaultConnection": "Server=127.0.0.1,5433;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word"
},
"ClientsCallBackUrls": {
"Spa": "http://localhost:5003"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

+ 10
- 0
src/Services/Identity/eShopOnContainers.Identity/bower.json View File

@ -0,0 +1,10 @@
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
}
}

+ 24
- 0
src/Services/Identity/eShopOnContainers.Identity/bundleconfig.json View File

@ -0,0 +1,24 @@
// Configure bundling and minification for the project.
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
[
{
"outputFileName": "wwwroot/css/site.min.css",
// An array of relative input file paths. Globbing patterns supported
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
// Optionally specify minification options
"minify": {
"enabled": true,
"renameLocals": true
},
// Optinally generate .map file
"sourceMap": false
}
]

+ 25
- 0
src/Services/Identity/eShopOnContainers.Identity/docker-compose.yml View File

@ -0,0 +1,25 @@
version: '2'
services:
identity.service:
image: eshop/identity
build:
context: .
dockerfile: Dockerfile
environment:
- ConnectionString=Server=identity.data;Initial Catalog=CatalogData;User Id=sa;Password=Pass@word
- Spa=http://localhost/5003
expose:
- "80"
ports:
- "5101:80"
depends_on:
- identity.data
identity.data:
image: microsoft/mssql-server-linux
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5434:1433"

+ 23
- 0
src/Services/Identity/eShopOnContainers.Identity/eShopOnContainers.Identity.xproj View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>a579e108-5445-403d-a407-339ac4d1611b</ProjectGuid>
<RootNamespace>eShopOnContainers.Identity</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

+ 105
- 0
src/Services/Identity/eShopOnContainers.Identity/project.json View File

@ -0,0 +1,105 @@
{
"userSecretsId": "aspnet-eShopOnContainers.Identity-90487118-103c-4ff0-b9da-e5e26f7ab0c5",
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.AspNetCore.Routing": "1.0.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer.Design": {
"version": "1.0.1",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"IdentityServer4.AspNetIdentity": "1.0.0-rc3",
"IdentityServer4.EntityFramework": "1.0.0-rc3"
},
"tools": {
"BundlerMinifier.Core": "2.0.238",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
"Microsoft.Extensions.SecretManager.Tools": "1.0.0-preview2-final",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final",
"imports": [
"portable-net45+win8"
]
}
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true,
"debugType": "portable"
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"Areas/**/Views",
"appsettings.json",
"web.config",
"Dockerfile",
"docker-compose.yml",
".dockerignore"
]
},
"scripts": {
"prepublish": [
"bower install",
"dotnet bundle"
],
"postpublish": [
"dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
]
}
}

+ 14
- 0
src/Services/Identity/eShopOnContainers.Identity/web.config View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>

+ 6
- 0
src/Services/Identity/eShopOnContainers.Identity/wwwroot/_references.js View File

@ -0,0 +1,6 @@
/// <autosync enabled="true" />
/// <reference path="js/site.js" />
/// <reference path="lib/bootstrap/dist/js/bootstrap.js" />
/// <reference path="lib/jquery/dist/jquery.js" />
/// <reference path="lib/jquery-validation/dist/jquery.validate.js" />
/// <reference path="lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js" />

+ 39
- 0
src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.css View File

@ -0,0 +1,39 @@
body {
margin-top: 65px;
}
.navbar-header {
position: relative;
top: -4px;
}
.navbar-brand > .icon-banner {
position: relative;
top: -2px;
display: inline;
}
.icon {
position: relative;
top: -10px;
}
.page-consent .client-logo {
float: left;
}
.page-consent .client-logo img {
width: 80px;
height: 80px;
}
.page-consent .consent-buttons {
margin-top: 25px;
}
.page-consent .consent-form .consent-scopecheck {
display: inline-block;
margin-right: 5px;
}
.page-consent .consent-form .consent-description {
margin-left: 25px;
}
.page-consent .consent-form .consent-description label {
font-weight: normal;
}
.page-consent .consent-form .consent-remember {
padding-left: 16px;
}

+ 57
- 0
src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.less View File

@ -0,0 +1,57 @@
body {
margin-top: 65px;
}
.navbar-header {
position:relative;
top:-4px;
}
.navbar-brand > .icon-banner {
position:relative;
top:-2px;
display:inline;
}
.icon {
position:relative;
top:-10px;
}
.page-consent {
.client-logo {
float: left;
img {
width: 80px;
height: 80px;
}
}
.consent-buttons {
margin-top: 25px;
}
.consent-form {
.consent-scopecheck {
display: inline-block;
margin-right: 5px;
}
.consent-scopecheck[disabled] {
//visibility:hidden;
}
.consent-description {
margin-left: 25px;
label {
font-weight: normal;
}
}
.consent-remember {
padding-left: 16px;
}
}
}

+ 1
- 0
src/Services/Identity/eShopOnContainers.Identity/wwwroot/css/site.min.css View File

@ -0,0 +1 @@
body{margin-top:65px}.navbar-header{position:relative;top:-4px}.navbar-brand>.icon-banner{position:relative;top:-2px;display:inline}.icon{position:relative;top:-10px}.page-consent .client-logo{float:left}.page-consent .client-logo img{width:80px;height:80px}.page-consent .consent-buttons{margin-top:25px}.page-consent .consent-form .consent-scopecheck{display:inline-block;margin-right:5px}.page-consent .consent-form .consent-description{margin-left:25px}.page-consent .consent-form .consent-description label{font-weight:normal}.page-consent .consent-form .consent-remember{padding-left:16px}

BIN
src/Services/Identity/eShopOnContainers.Identity/wwwroot/favicon.ico View File

Before After

BIN
src/Services/Identity/eShopOnContainers.Identity/wwwroot/icon.jpg View File

Before After
Width: 64  |  Height: 64  |  Size: 19 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save