16 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
17 | used in an authenticator app you should reset your authenticator keys.
18 |
19 |
20 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/UserManagementReact.Entities/ApplicationUser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.ComponentModel.DataAnnotations;
5 | using System.ComponentModel.DataAnnotations.Schema;
6 | using System.Text;
7 |
8 | namespace UserManagementReact.Entities
9 | {
10 | // Add profile data for application users by adding properties to the ApplicationUser class
11 | public class ApplicationUser : IdentityUser
12 | {
13 | [MaxLength(100)]
14 | public string FirstName { get; set; }
15 | [MaxLength(100)]
16 | public string LastName { get; set; }
17 | public bool ApplicationEditingAllowed { get; set; } = true;
18 | public bool Approved { get; set; } = false;
19 |
20 | [Timestamp]
21 | public byte[] RowVersion { get; set; }
22 |
23 | [NotMapped]
24 | public string FullName
25 | {
26 | get
27 | {
28 | return FirstName + " " + LastName;
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ShowRecoveryCodesModel
3 | @{
4 | ViewData["Title"] = "Recovery codes";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 | Put these codes in a safe place.
13 |
14 |
15 | If you lose your device and don't have the recovery codes you will lose access to your account.
16 |
19 | Swapping to Development environment will display more detailed information about the error that occurred.
20 |
21 |
22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
23 |
24 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 | import translations from "./translations.js";
4 |
5 | // the translations
6 | // (tip move them in a JSON file and import them)
7 | //const resources = {
8 | // en: {
9 | // translation: {
10 | // "Welcome to React": "Welcome to React and react-i18next",
11 | // "Hello": "Hi there!",
12 | // "Logout": "Get out of here"
13 | // }
14 | // },
15 | // el: {
16 | // translation: {
17 | // "Hello": "Γεια σου!",
18 | // "Logout": "Αποσύνδεση"
19 | // }
20 | // }
21 | //};
22 |
23 | const resources = translations;
24 |
25 | i18n
26 | .use(initReactI18next) // passes i18n down to react-i18next
27 | .init({
28 | resources,
29 | lng: "en",
30 |
31 | keySeparator: false, // we do not use keys in form messages.welcome
32 |
33 | interpolation: {
34 | escapeValue: false // react already safes from xss
35 | }
36 | });
37 |
38 | export default i18n;
--------------------------------------------------------------------------------
/UserManagementReact/Helpers/Cultures.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 |
4 | namespace UserManagementReact.Helpers
5 | {
6 | public static class Cultures
7 | {
8 | public struct CulturePair
9 | {
10 | public string Code { get; set; }
11 | public string LocalName { get; set; }
12 | }
13 | public static List CulturePairs = new List()
14 | {
15 | new CulturePair() { Code = "en", LocalName = "English" },
16 | new CulturePair() { Code = "el", LocalName = "Ελληνικά" }
17 | };
18 |
19 | public static string DefaultCulture = "el";
20 |
21 | public static List SupportedCultures
22 | {
23 | get
24 | {
25 | List returnList = new List();
26 |
27 | foreach (CulturePair culturePair in CulturePairs)
28 | {
29 | returnList.Add(new CultureInfo(culturePair.Code));
30 | }
31 | return returnList;
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/UserManagementReact/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ErrorModel
3 | @{
4 | ViewData["Title"] = "Error";
5 | }
6 |
7 |
Error.
8 |
An error occurred while processing your request.
9 |
10 | @if (Model.ShowRequestId)
11 | {
12 |
13 | Request ID:@Model.RequestId
14 |
15 | }
16 |
17 |
Development Mode
18 |
19 | Swapping to the Development environment displays detailed information about the error that occurred.
20 |
21 |
22 | The Development environment shouldn't be enabled for deployed applications.
23 | It can result in displaying sensitive information from exceptions to end users.
24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
25 | and restarting the app.
26 |
24 |
25 | @section Scripts {
26 |
27 | }
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/index.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.css';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import './i18n';
6 | import App from './App';
7 | //import registerServiceWorker from './registerServiceWorker';
8 |
9 | const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
10 | const rootElement = document.getElementById('root');
11 |
12 | ReactDOM.render(
13 |
14 |
15 | ,
16 | rootElement);
17 |
18 | // Uncomment the line above that imports the registerServiceWorker function
19 | // and the line below to register the generated service worker.
20 | // By default create-react-app includes a service worker to improve the
21 | // performance of the application by caching static assets. This service
22 | // worker can interfere with the Identity UI, so it is
23 | // disabled by default when Identity is being used.
24 | //
25 | //registerServiceWorker();
26 |
27 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ResetAuthenticatorModel
3 | @{
4 | ViewData["Title"] = "Reset authenticator key";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 |
13 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
14 |
15 |
16 | This process disables 2FA until you verify your authenticator app.
17 | If you do not complete your authenticator app configuration you may lose access to your account.
18 |
10 | You have requested to log in with a recovery code. This login will not be remembered until you provide
11 | an authenticator app code at log in or disable 2FA and log in again.
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 | @section Scripts {
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/UserManagementReact/ScaffoldingReadme.txt:
--------------------------------------------------------------------------------
1 | Support for ASP.NET Core Identity was added to your project
2 | - The code for adding Identity to your project was generated under Areas/Identity.
3 |
4 | Configuration of the Identity related services can be found in the Areas/Identity/IdentityHostingStartup.cs file.
5 |
6 |
7 | The generated UI requires support for static files. To add static files to your app:
8 | 1. Call app.UseStaticFiles() from your Configure method
9 |
10 | To use ASP.NET Core Identity you also need to enable authentication. To authentication to your app:
11 | 1. Call app.UseAuthentication() from your Configure method (after static files)
12 |
13 | The generated UI requires MVC. To add MVC to your app:
14 | 1. Call services.AddMvc() from your ConfigureServices method
15 | 2. Call app.UseRouting() at the top your Configure method, and UseEndpoints() after authentication:
16 | app.UseEndpoints(endpoints =>
17 | {
18 | endpoints.MapControllers();
19 | endpoints.MapRazorPages();
20 | });
21 |
22 | Apps that use ASP.NET Core Identity should also use HTTPS. To enable HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
23 |
24 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model GenerateRecoveryCodesModel
3 | @{
4 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 |
13 | Put these codes in a safe place.
14 |
15 |
16 | If you lose your device and don't have the recovery codes you will lose access to your account.
17 |
18 |
19 | Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
20 | used in an authenticator app you should reset your authenticator keys.
21 |
27 |
28 | @section Scripts {
29 |
30 | }
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Route } from 'react-router';
3 | import { Layout } from './components/Layout';
4 | import { Home } from './components/Home';
5 | import { FetchData } from './components/FetchData';
6 | import { Counter } from './components/Counter';
7 | import { UsersRouter } from './components/users/UsersRouter';
8 | import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
9 | import ApiAuthorizationRoutes from './components/api-authorization/ApiAuthorizationRoutes';
10 | import { ApplicationPaths } from './components/api-authorization/ApiAuthorizationConstants';
11 |
12 |
13 | import './custom.css'
14 |
15 | export default class App extends Component {
16 | static displayName = App.name;
17 |
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using UserManagementReact.Entities;
3 | using Microsoft.AspNetCore.Identity;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.AspNetCore.Mvc.RazorPages;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
9 | {
10 | public class PersonalDataModel : PageModel
11 | {
12 | private readonly UserManager _userManager;
13 | private readonly ILogger _logger;
14 |
15 | public PersonalDataModel(
16 | UserManager userManager,
17 | ILogger logger)
18 | {
19 | _userManager = userManager;
20 | _logger = logger;
21 | }
22 |
23 | public async Task OnGet()
24 | {
25 | var user = await _userManager.GetUserAsync(User);
26 | if (user == null)
27 | {
28 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
29 | }
30 |
31 | return Page();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/UserManagementReact/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ExternalLogin.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginModel
3 | @{
4 | ViewData["Title"] = "Register";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
Associate your @Model.LoginProvider account.
9 |
10 |
11 |
12 | You've successfully authenticated with @Model.LoginProvider.
13 | Please enter an email address for this site below and click the Register button to finish
14 | logging in.
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 | @section Scripts {
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/UserManagementReact/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/UserManagementReact/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace UserManagementReact.Controllers
10 | {
11 | [Authorize]
12 | [ApiController]
13 | [Route("api/[controller]")]
14 | public class WeatherForecastController : ControllerBase
15 | {
16 | private static readonly string[] Summaries = new[]
17 | {
18 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
19 | };
20 |
21 | private readonly ILogger _logger;
22 |
23 | public WeatherForecastController(ILogger logger)
24 | {
25 | _logger = logger;
26 | }
27 |
28 | [HttpGet]
29 | public IEnumerable Get()
30 | {
31 | var rng = new Random();
32 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
33 | {
34 | Date = DateTime.Now.AddDays(index),
35 | TemperatureC = rng.Next(-20, 55),
36 | Summary = Summaries[rng.Next(Summaries.Length)]
37 | })
38 | .ToArray();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/UserManagementReact.Tests/Helpers/TestAuthHandler.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authentication;
2 | using Microsoft.Extensions.Logging;
3 | using Microsoft.Extensions.Options;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Security.Claims;
7 | using System.Text;
8 | using System.Text.Encodings.Web;
9 | using System.Threading.Tasks;
10 |
11 | namespace UserManagementReact.Tests.Helpers
12 | {
13 | class TestAuthHandler : AuthenticationHandler
14 | {
15 | public TestAuthHandler(IOptionsMonitor options,
16 | ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
17 | : base(options, logger, encoder, clock)
18 | {
19 | }
20 | protected override Task HandleAuthenticateAsync()
21 | {
22 | var claims = new[]
23 | {
24 | new Claim(ClaimTypes.Name, "Test user"),
25 | new Claim(ClaimTypes.Role, "administrator")
26 | };
27 |
28 | var identity = new ClaimsIdentity(claims, "Test");
29 | var principal = new ClaimsPrincipal(identity);
30 | var ticket = new AuthenticationTicket(principal, "Test");
31 |
32 | var result = AuthenticateResult.Success(ticket);
33 |
34 | return Task.FromResult(result);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using System.Text;
3 | using System.Threading.Tasks;
4 | using UserManagementReact.Entities;
5 | using Microsoft.AspNetCore.Identity.UI.Services;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.AspNetCore.WebUtilities;
9 | using Microsoft.AspNetCore.Identity;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account
12 | {
13 | [AllowAnonymous]
14 | public class RegisterConfirmationModel : PageModel
15 | {
16 | private readonly UserManager _userManager;
17 | private readonly IEmailSender _sender;
18 |
19 | public RegisterConfirmationModel(UserManager userManager, IEmailSender sender)
20 | {
21 | _userManager = userManager;
22 | _sender = sender;
23 | }
24 |
25 | public string Email { get; set; }
26 |
27 | public bool DisplayConfirmAccountLink { get; set; }
28 |
29 | public string EmailConfirmationUrl { get; set; }
30 |
31 | public async Task OnGetAsync()
32 | {
33 | return Page();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/users/UsersDelete.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { withRouter } from 'react-router';
3 | import usersService from './UsersService';
4 | import { Button } from 'reactstrap';
5 |
6 | class UsersDeletePlain extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = { users: [], loading: true };
10 |
11 | const { match } = this.props;
12 | this.userId = match.params.userId;
13 | }
14 |
15 | componentDidMount() {
16 |
17 | }
18 |
19 | handleClickOk = () => {
20 | const { history } = this.props;
21 |
22 | (async () => {
23 | await usersService.deleteUser(this.userId);
24 | history.push('/users');
25 | })();
26 |
27 | //usersService.deleteUser(this.userId)
28 | // .then(() => history.push('/users'));
29 | }
30 |
31 | handleClickCancel = () => {
32 | const { history } = this.props;
33 |
34 | history.push('/users');
35 | }
36 |
37 | render() {
38 | return (
39 |
38 |
39 | @section Scripts {
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/UserManagementReact/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | UserManagementReact
24 |
25 |
26 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export class Home extends Component {
4 | static displayName = Home.name;
5 |
6 | render () {
7 | return (
8 |
9 |
Hello, world!
10 |
Welcome to your new single-page application, built with:
Client-side navigation. For example, click Counter then Back to return here.
19 |
Development server integration. In development mode, the development server from create-react-app runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.
20 |
Efficient production builds. In production mode, development-time features are disabled, and your dotnet publish configuration produces minified, efficiently bundled JavaScript files.
21 |
22 |
The ClientApp subdirectory is a standard React application based on the create-react-app template. If you open a command prompt in that directory, you can run npm commands such as npm test or npm install.
72 |
73 |
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | export const NavMenu = withTranslation()(NavMenuPlain);
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/api-authorization/LoginMenu.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { NavItem, NavLink } from 'reactstrap';
3 | import { Link } from 'react-router-dom';
4 | import authService from './AuthorizeService';
5 | import { ApplicationPaths } from './ApiAuthorizationConstants';
6 | import { withTranslation } from 'react-i18next';
7 |
8 | class LoginMenuPlain extends Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | isAuthenticated: false,
14 | userName: null
15 | };
16 | }
17 |
18 | componentDidMount() {
19 | this._subscription = authService.subscribe(() => this.populateState());
20 | this.populateState();
21 | }
22 |
23 | componentWillUnmount() {
24 | authService.unsubscribe(this._subscription);
25 | }
26 |
27 | async populateState() {
28 | const [isAuthenticated, user] = await Promise.all([authService.isAuthenticated(), authService.getUser()])
29 | this.setState({
30 | isAuthenticated,
31 | userName: user && user.name
32 | });
33 | }
34 |
35 | render() {
36 | const { isAuthenticated, userName } = this.state;
37 | if (!isAuthenticated) {
38 | const registerPath = `${ApplicationPaths.Register}`;
39 | const loginPath = `${ApplicationPaths.Login}`;
40 | return this.anonymousView(registerPath, loginPath);
41 | } else {
42 | const profilePath = `${ApplicationPaths.Profile}`;
43 | const logoutPath = { pathname: `${ApplicationPaths.LogOut}`, state: { local: true } };
44 | return this.authenticatedView(userName, profilePath, logoutPath);
45 | }
46 | }
47 |
48 | authenticatedView(userName, profilePath, logoutPath) {
49 | const { t, i18n } = this.props;
50 |
51 | return (
52 |
53 |
54 | {t('Hello')} {userName}
55 |
56 |
57 |
58 |
59 | {t('Logout')}
60 |
61 |
62 | );
63 | }
64 |
65 | anonymousView(registerPath, loginPath) {
66 | return (
67 |
68 | Register
69 |
70 |
71 | Login
72 |
73 | );
74 | }
75 | }
76 |
77 | export const LoginMenu = withTranslation()(LoginMenuPlain);
78 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model EnableAuthenticatorModel
3 | @{
4 | ViewData["Title"] = "Configure authenticator app";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
To use an authenticator app go through the following steps:
12 |
13 |
14 |
15 | Download a two-factor authenticator app like Microsoft Authenticator for
16 | Windows Phone,
17 | Android and
18 | iOS or
19 | Google Authenticator for
20 | Android and
21 | iOS.
22 |
23 |
24 |
25 |
Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
32 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
33 | with a unique code. Enter the code in the confirmation box below.
34 |
67 | There are no external authentication services configured. See this article
68 | for details on setting up this ASP.NET application to support logging in via external services.
69 |
89 |
90 | @section Scripts {
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Authorization;
8 | using UserManagementReact.Entities;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.AspNetCore.Mvc.RazorPages;
12 | using Microsoft.AspNetCore.WebUtilities;
13 |
14 | namespace UserManagementReact.Areas.Identity.Pages.Account
15 | {
16 | [AllowAnonymous]
17 | public class ResetPasswordModel : PageModel
18 | {
19 | private readonly UserManager _userManager;
20 |
21 | public ResetPasswordModel(UserManager userManager)
22 | {
23 | _userManager = userManager;
24 | }
25 |
26 | [BindProperty]
27 | public InputModel Input { get; set; }
28 |
29 | public class InputModel
30 | {
31 | [Required]
32 | [EmailAddress]
33 | public string Email { get; set; }
34 |
35 | [Required]
36 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
37 | [DataType(DataType.Password)]
38 | public string Password { get; set; }
39 |
40 | [DataType(DataType.Password)]
41 | [Display(Name = "Confirm password")]
42 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
43 | public string ConfirmPassword { get; set; }
44 |
45 | public string Code { get; set; }
46 | }
47 |
48 | public IActionResult OnGet(string code = null)
49 | {
50 | if (code == null)
51 | {
52 | return BadRequest("A code must be supplied for password reset.");
53 | }
54 | else
55 | {
56 | Input = new InputModel
57 | {
58 | Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
59 | };
60 | return Page();
61 | }
62 | }
63 |
64 | public async Task OnPostAsync()
65 | {
66 | if (!ModelState.IsValid)
67 | {
68 | return Page();
69 | }
70 |
71 | var user = await _userManager.FindByEmailAsync(Input.Email);
72 | if (user == null)
73 | {
74 | // Don't reveal that the user does not exist
75 | return RedirectToPage("./ResetPasswordConfirmation");
76 | }
77 |
78 | var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
79 | if (result.Succeeded)
80 | {
81 | return RedirectToPage("./ResetPasswordConfirmation");
82 | }
83 |
84 | foreach (var error in result.Errors)
85 | {
86 | ModelState.AddModelError(string.Empty, error.Description);
87 | }
88 | return Page();
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/UserManagementReact.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29403.142
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserManagementReact", "UserManagementReact\UserManagementReact.csproj", "{2B89F4E0-3DEF-491F-8FA3-42DB74AC1AB1}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserManagementReact.Entities", "UserManagementReact.Entities\UserManagementReact.Entities.csproj", "{3F59E578-375A-4E07-832F-C28D9F276644}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserManagementReact.Services", "UserManagementReact.Services\UserManagementReact.Services.csproj", "{0003DF02-5AF0-4DB8-8FB4-366BE6B2920A}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserManagementReact.Tests", "UserManagementReact.Tests\UserManagementReact.Tests.csproj", "{0143CE4F-56D4-4C8F-A131-2A0D0A2D2E05}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserManagementReact.ServicesTests", "UserManagementReact.ServicesTests\UserManagementReact.ServicesTests.csproj", "{2547010E-A691-473E-8355-02EFBBD3F639}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {2B89F4E0-3DEF-491F-8FA3-42DB74AC1AB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {2B89F4E0-3DEF-491F-8FA3-42DB74AC1AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {2B89F4E0-3DEF-491F-8FA3-42DB74AC1AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {2B89F4E0-3DEF-491F-8FA3-42DB74AC1AB1}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {3F59E578-375A-4E07-832F-C28D9F276644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {3F59E578-375A-4E07-832F-C28D9F276644}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {3F59E578-375A-4E07-832F-C28D9F276644}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {3F59E578-375A-4E07-832F-C28D9F276644}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {0003DF02-5AF0-4DB8-8FB4-366BE6B2920A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {0003DF02-5AF0-4DB8-8FB4-366BE6B2920A}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {0003DF02-5AF0-4DB8-8FB4-366BE6B2920A}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {0003DF02-5AF0-4DB8-8FB4-366BE6B2920A}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {0143CE4F-56D4-4C8F-A131-2A0D0A2D2E05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {0143CE4F-56D4-4C8F-A131-2A0D0A2D2E05}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {0143CE4F-56D4-4C8F-A131-2A0D0A2D2E05}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {0143CE4F-56D4-4C8F-A131-2A0D0A2D2E05}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {2547010E-A691-473E-8355-02EFBBD3F639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {2547010E-A691-473E-8355-02EFBBD3F639}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {2547010E-A691-473E-8355-02EFBBD3F639}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {2547010E-A691-473E-8355-02EFBBD3F639}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {EE167AB2-E14A-400B-82F2-86BCCDA3E79F}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class SetPasswordModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 |
18 | public SetPasswordModel(
19 | UserManager userManager,
20 | SignInManager signInManager)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | }
25 |
26 | [BindProperty]
27 | public InputModel Input { get; set; }
28 |
29 | [TempData]
30 | public string StatusMessage { get; set; }
31 |
32 | public class InputModel
33 | {
34 | [Required]
35 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
36 | [DataType(DataType.Password)]
37 | [Display(Name = "New password")]
38 | public string NewPassword { get; set; }
39 |
40 | [DataType(DataType.Password)]
41 | [Display(Name = "Confirm new password")]
42 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
43 | public string ConfirmPassword { get; set; }
44 | }
45 |
46 | public async Task OnGetAsync()
47 | {
48 | var user = await _userManager.GetUserAsync(User);
49 | if (user == null)
50 | {
51 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
52 | }
53 |
54 | var hasPassword = await _userManager.HasPasswordAsync(user);
55 |
56 | if (hasPassword)
57 | {
58 | return RedirectToPage("./ChangePassword");
59 | }
60 |
61 | return Page();
62 | }
63 |
64 | public async Task OnPostAsync()
65 | {
66 | if (!ModelState.IsValid)
67 | {
68 | return Page();
69 | }
70 |
71 | var user = await _userManager.GetUserAsync(User);
72 | if (user == null)
73 | {
74 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
75 | }
76 |
77 | var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
78 | if (!addPasswordResult.Succeeded)
79 | {
80 | foreach (var error in addPasswordResult.Errors)
81 | {
82 | ModelState.AddModelError(string.Empty, error.Description);
83 | }
84 | return Page();
85 | }
86 |
87 | await _signInManager.RefreshSignInAsync(user);
88 | StatusMessage = "Your password has been set.";
89 |
90 | return RedirectToPage();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public partial class IndexModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 |
18 | public IndexModel(
19 | UserManager userManager,
20 | SignInManager signInManager)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | }
25 |
26 | public string Username { get; set; }
27 |
28 | [TempData]
29 | public string StatusMessage { get; set; }
30 |
31 | [BindProperty]
32 | public InputModel Input { get; set; }
33 |
34 | public class InputModel
35 | {
36 | [Phone]
37 | [Display(Name = "Phone number")]
38 | public string PhoneNumber { get; set; }
39 | }
40 |
41 | private async Task LoadAsync(ApplicationUser user)
42 | {
43 | var userName = await _userManager.GetUserNameAsync(user);
44 | var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
45 |
46 | Username = userName;
47 |
48 | Input = new InputModel
49 | {
50 | PhoneNumber = phoneNumber
51 | };
52 | }
53 |
54 | public async Task OnGetAsync()
55 | {
56 | var user = await _userManager.GetUserAsync(User);
57 | if (user == null)
58 | {
59 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
60 | }
61 |
62 | await LoadAsync(user);
63 | return Page();
64 | }
65 |
66 | public async Task OnPostAsync()
67 | {
68 | var user = await _userManager.GetUserAsync(User);
69 | if (user == null)
70 | {
71 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
72 | }
73 |
74 | if (!ModelState.IsValid)
75 | {
76 | await LoadAsync(user);
77 | return Page();
78 | }
79 |
80 | var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
81 | if (Input.PhoneNumber != phoneNumber)
82 | {
83 | var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
84 | if (!setPhoneResult.Succeeded)
85 | {
86 | var userId = await _userManager.GetUserIdAsync(user);
87 | throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
88 | }
89 | }
90 |
91 | await _signInManager.RefreshSignInAsync(user);
92 | StatusMessage = "Your profile has been updated";
93 | return RedirectToPage();
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class LoginWithRecoveryCodeModel : PageModel
17 | {
18 | private readonly SignInManager _signInManager;
19 | private readonly ILogger _logger;
20 |
21 | public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger)
22 | {
23 | _signInManager = signInManager;
24 | _logger = logger;
25 | }
26 |
27 | [BindProperty]
28 | public InputModel Input { get; set; }
29 |
30 | public string ReturnUrl { get; set; }
31 |
32 | public class InputModel
33 | {
34 | [BindProperty]
35 | [Required]
36 | [DataType(DataType.Text)]
37 | [Display(Name = "Recovery Code")]
38 | public string RecoveryCode { get; set; }
39 | }
40 |
41 | public async Task OnGetAsync(string returnUrl = null)
42 | {
43 | // Ensure the user has gone through the username & password screen first
44 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
45 | if (user == null)
46 | {
47 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
48 | }
49 |
50 | ReturnUrl = returnUrl;
51 |
52 | return Page();
53 | }
54 |
55 | public async Task OnPostAsync(string returnUrl = null)
56 | {
57 | if (!ModelState.IsValid)
58 | {
59 | return Page();
60 | }
61 |
62 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
63 | if (user == null)
64 | {
65 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
66 | }
67 |
68 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
69 |
70 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
71 |
72 | if (result.Succeeded)
73 | {
74 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
75 | return LocalRedirect(returnUrl ?? Url.Content("~/"));
76 | }
77 | if (result.IsLockedOut)
78 | {
79 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
80 | return RedirectToPage("./Lockout");
81 | }
82 | else
83 | {
84 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
85 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
86 | return Page();
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/Pager.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
3 |
4 | export class Pager extends Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | static defaultProps = {
10 | page: 1,
11 | totalItems: 100,
12 | pageSize: 10,
13 | maxPages: 5
14 | }
15 |
16 | componentDidMount() {
17 | }
18 |
19 | paginate() {
20 | const { totalItems, page, pageSize, maxPages } = this.props;
21 | let currentPage = page;
22 |
23 | // calculate total pages
24 | let totalPages = Math.ceil(totalItems / pageSize);
25 |
26 | // ensure current page isn't out of range
27 | if (currentPage < 1) {
28 | currentPage = 1;
29 | } else if (currentPage > totalPages) {
30 | currentPage = totalPages;
31 | }
32 |
33 | let startPage, endPage;
34 |
35 | if (totalPages <= maxPages) {
36 | // total pages less than max so show all pages
37 | startPage = 1;
38 | endPage = totalPages;
39 | } else {
40 | // total pages more than max so calculate start and end pages
41 | let maxPagesBeforeCurrentPage = Math.floor(maxPages / 2);
42 | let maxPagesAfterCurrentPage = Math.ceil(maxPages / 2) - 1;
43 | if (currentPage <= maxPagesBeforeCurrentPage) {
44 | // current page near the start
45 | startPage = 1;
46 | endPage = maxPages;
47 | } else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
48 | // current page near the end
49 | startPage = totalPages - maxPages + 1;
50 | endPage = totalPages;
51 | } else {
52 | // current page somewhere in the middle
53 | startPage = currentPage - maxPagesBeforeCurrentPage;
54 | endPage = currentPage + maxPagesAfterCurrentPage;
55 | }
56 | }
57 |
58 | // calculate start and end item indexes
59 | let startIndex = (currentPage - 1) * pageSize;
60 | let endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);
61 |
62 | // create an array of pages to ng-repeat in the pager control
63 | let pages = Array.from(Array((endPage + 1) - startPage).keys()).map(i => startPage + i);
64 |
65 | // return object with all pager properties required by the view
66 | return {
67 | totalItems: totalItems,
68 | currentPage: currentPage,
69 | pageSize: pageSize,
70 | totalPages: totalPages,
71 | startPage: startPage,
72 | endPage: endPage,
73 | startIndex: startIndex,
74 | endIndex: endIndex,
75 | pages: pages
76 | }
77 | }
78 |
79 | handlePageChange = (newPage) => {
80 | this.props.handlePageChange(newPage);
81 | }
82 |
83 | render() {
84 | const { currentPage, totalPages, pages } = this.paginate();
85 |
86 | return (
87 |
88 | currentPage > 1 && this.handlePageChange(1)}>
89 |
90 |
91 | currentPage > 1 && this.handlePageChange(currentPage - 1)} >
92 |
93 |
94 |
95 | {pages.map((value, index) =>
96 | this.handlePageChange(value)}>
97 |
98 | {value}
99 |
100 |
101 | )}
102 |
103 | currentPage < totalPages && this.handlePageChange(currentPage + 1)} >
104 |
105 |
106 | currentPage < totalPages && this.handlePageChange(totalPages)}>
107 |
108 |
109 |
110 | )
111 | }
112 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class LoginWith2faModel : PageModel
17 | {
18 | private readonly SignInManager _signInManager;
19 | private readonly ILogger _logger;
20 |
21 | public LoginWith2faModel(SignInManager signInManager, ILogger logger)
22 | {
23 | _signInManager = signInManager;
24 | _logger = logger;
25 | }
26 |
27 | [BindProperty]
28 | public InputModel Input { get; set; }
29 |
30 | public bool RememberMe { get; set; }
31 |
32 | public string ReturnUrl { get; set; }
33 |
34 | public class InputModel
35 | {
36 | [Required]
37 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
38 | [DataType(DataType.Text)]
39 | [Display(Name = "Authenticator code")]
40 | public string TwoFactorCode { get; set; }
41 |
42 | [Display(Name = "Remember this machine")]
43 | public bool RememberMachine { get; set; }
44 | }
45 |
46 | public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
47 | {
48 | // Ensure the user has gone through the username & password screen first
49 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
50 |
51 | if (user == null)
52 | {
53 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
54 | }
55 |
56 | ReturnUrl = returnUrl;
57 | RememberMe = rememberMe;
58 |
59 | return Page();
60 | }
61 |
62 | public async Task OnPostAsync(bool rememberMe, string returnUrl = null)
63 | {
64 | if (!ModelState.IsValid)
65 | {
66 | return Page();
67 | }
68 |
69 | returnUrl = returnUrl ?? Url.Content("~/");
70 |
71 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
72 | if (user == null)
73 | {
74 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
75 | }
76 |
77 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
78 |
79 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
80 |
81 | if (result.Succeeded)
82 | {
83 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
84 | return LocalRedirect(returnUrl);
85 | }
86 | else if (result.IsLockedOut)
87 | {
88 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
89 | return RedirectToPage("./Lockout");
90 | }
91 | else
92 | {
93 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
94 | ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
95 | return Page();
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class ChangePasswordModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 | private readonly ILogger _logger;
18 |
19 | public ChangePasswordModel(
20 | UserManager userManager,
21 | SignInManager signInManager,
22 | ILogger logger)
23 | {
24 | _userManager = userManager;
25 | _signInManager = signInManager;
26 | _logger = logger;
27 | }
28 |
29 | [BindProperty]
30 | public InputModel Input { get; set; }
31 |
32 | [TempData]
33 | public string StatusMessage { get; set; }
34 |
35 | public class InputModel
36 | {
37 | [Required]
38 | [DataType(DataType.Password)]
39 | [Display(Name = "Current password")]
40 | public string OldPassword { get; set; }
41 |
42 | [Required]
43 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
44 | [DataType(DataType.Password)]
45 | [Display(Name = "New password")]
46 | public string NewPassword { get; set; }
47 |
48 | [DataType(DataType.Password)]
49 | [Display(Name = "Confirm new password")]
50 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
51 | public string ConfirmPassword { get; set; }
52 | }
53 |
54 | public async Task OnGetAsync()
55 | {
56 | var user = await _userManager.GetUserAsync(User);
57 | if (user == null)
58 | {
59 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
60 | }
61 |
62 | var hasPassword = await _userManager.HasPasswordAsync(user);
63 | if (!hasPassword)
64 | {
65 | return RedirectToPage("./SetPassword");
66 | }
67 |
68 | return Page();
69 | }
70 |
71 | public async Task OnPostAsync()
72 | {
73 | if (!ModelState.IsValid)
74 | {
75 | return Page();
76 | }
77 |
78 | var user = await _userManager.GetUserAsync(User);
79 | if (user == null)
80 | {
81 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
82 | }
83 |
84 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
85 | if (!changePasswordResult.Succeeded)
86 | {
87 | foreach (var error in changePasswordResult.Errors)
88 | {
89 | ModelState.AddModelError(string.Empty, error.Description);
90 | }
91 | return Page();
92 | }
93 |
94 | await _signInManager.RefreshSignInAsync(user);
95 | _logger.LogInformation("User changed their password successfully.");
96 | StatusMessage = "Your password has been changed.";
97 |
98 | return RedirectToPage();
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/UserManagementReact/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Logging;
10 | using UserManagementReact.Services;
11 | using UserManagementReact.Entities;
12 | using Microsoft.AspNetCore.Identity;
13 | using static UserManagementReact.Services.Helpers.RoleHelpers;
14 | using UserManagementReact.Services.Helpers;
15 | using Microsoft.EntityFrameworkCore;
16 |
17 | namespace UserManagementReact
18 | {
19 | public class Program
20 | {
21 | public static void Main(string[] args)
22 | {
23 | // CreateWebHostBuilder(args).Build().Run();
24 |
25 | var hostBuilder = CreateWebHostBuilder(args);
26 | var host = hostBuilder.Build();
27 |
28 | InitializeDatabase(host);
29 |
30 | host.Run();
31 | }
32 |
33 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
34 | WebHost.CreateDefaultBuilder(args)
35 | //.ConfigureLogging(logging =>
36 | //{
37 | // logging.ClearProviders();
38 | // logging.AddConsole();
39 | //})
40 | .UseStartup();
41 |
42 | private static void InitializeDatabase(IWebHost host)
43 | {
44 | using (var serviceScope = host.Services.CreateScope())
45 | {
46 | var services = serviceScope.ServiceProvider;
47 |
48 | if (!services.GetService().AllMigrationsApplied())
49 | {
50 | services.GetService().Database.Migrate();
51 | }
52 |
53 | // Seed database
54 | serviceScope.ServiceProvider.GetService().EnsureSeeded();
55 |
56 | IUserManagementService umService = services.GetRequiredService();
57 | var usersCount = umService.GetAllUsersCountAsync("").Result;
58 | if (usersCount == 0)
59 | {
60 | RoleManager roleManager = services.GetRequiredService>();
61 |
62 | foreach (RolePair role in RoleHelpers.Roles)
63 | {
64 | if (!roleManager.RoleExistsAsync(role.Name).Result)
65 | {
66 | var idRole = new IdentityRole(role.Name);
67 | roleManager.CreateAsync(idRole).Wait();
68 | }
69 | }
70 |
71 | // Create admin user
72 | ApplicationUser adminUser = new ApplicationUser
73 | {
74 | UserName = "admin@domain.com",
75 | Email = "admin@domain.com",
76 | FirstName = "AdminFirst",
77 | LastName = "AdminLast",
78 | EmailConfirmed = true,
79 | Approved = true
80 | };
81 |
82 | umService.AddUserAsync(adminUser, "admin", "administrator").Wait(); // username = "admin", password = "admin"
83 |
84 | UserManager userManager = services.GetRequiredService>();
85 | userManager.AddToRoleAsync(adminUser, "user").Wait();
86 |
87 | ApplicationUser supervisorUser = new ApplicationUser
88 | {
89 | UserName = "supervisor@domain.com",
90 | Email = "supervisor@domain.com",
91 | FirstName = "SupervisorFirst",
92 | LastName = "SupervisorLast",
93 | EmailConfirmed = true,
94 | Approved = true
95 | };
96 |
97 | umService.AddUserAsync(supervisorUser, "supervisor", "supervisor").Wait();
98 |
99 | ApplicationUser userUser = new ApplicationUser();
100 |
101 | // Create users
102 | for (int i = 1; i < 125; i++)
103 | {
104 | userUser = new ApplicationUser
105 | {
106 | UserName = "user" + i + "@domain.com",
107 | Email = "user" + i + "@domain.com",
108 | FirstName = "USER" + i + "FIRST",
109 | LastName = "USER" + i + "LAST",
110 | EmailConfirmed = true,
111 | Approved = true
112 | };
113 | umService.AddUserAsync(userUser, "user", "user").Wait();
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/UserManagementReact.ServicesTests/Helpers/Helpers.cs:
--------------------------------------------------------------------------------
1 | using UserManagementReact.Entities;
2 | using UserManagementReact.Services;
3 | using IdentityServer4.EntityFramework.Options;
4 | using Microsoft.AspNetCore.Identity;
5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
6 | using Microsoft.EntityFrameworkCore;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Logging;
9 | using Microsoft.Extensions.Options;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Text;
13 |
14 | namespace UserManagementReact.ServicesTests.Helpers
15 | {
16 | class TestHelpers
17 | {
18 | public static ApplicationDbContext CreateDbContext()
19 | {
20 | // Create a new service provider to create a new in-memory database.
21 | var serviceProvider = new ServiceCollection()
22 | .AddEntityFrameworkInMemoryDatabase()
23 | .BuildServiceProvider();
24 |
25 | // Create a new options instance using an in-memory database and
26 | // IServiceProvider that the context should resolve all of its
27 | // services from.
28 | var builder = new DbContextOptionsBuilder()
29 | .UseInMemoryDatabase("InMemoryDb")
30 | .UseInternalServiceProvider(serviceProvider);
31 |
32 | var operationalStoreOptions = new IdentityServer4.EntityFramework.Options.OperationalStoreOptions()
33 | {
34 | ConfigureDbContext = optionsx =>
35 | {
36 | optionsx.UseInMemoryDatabase("InMemoryDb");
37 | optionsx.UseInternalServiceProvider(serviceProvider);
38 | }
39 | };
40 |
41 | var dbContextOptions = builder.Options;
42 | var identityServerOptions = Options.Create(operationalStoreOptions);
43 |
44 | return new ApplicationDbContext(dbContextOptions, identityServerOptions);
45 | }
46 | public static UserManager CreateUserManager(ApplicationDbContext context)
47 | {
48 | var userStore = new UserStore(context);
49 | var passwordHasher = new PasswordHasher();
50 | var userValidators = new List> { new UserValidator() };
51 | var passwordValidators = new List> { new PasswordValidator() };
52 | var userLogger = (new LoggerFactory()).CreateLogger>();
53 |
54 | return new UserManager(userStore, null, passwordHasher, userValidators, passwordValidators,
55 | null, null, null, userLogger);
56 | }
57 |
58 | public static RoleManager CreateRoleManager(ApplicationDbContext context)
59 | {
60 | var roleStore = new RoleStore(context);
61 | var roleValidators = new List> { new RoleValidator() };
62 | var roleLogger = (new LoggerFactory()).CreateLogger>();
63 |
64 | return new RoleManager(roleStore, roleValidators, null, null, roleLogger);
65 | }
66 | public static List GetTestUsers()
67 | {
68 | List returnList = new List();
69 |
70 | returnList.Add(new ApplicationUser
71 | {
72 | FirstName = "aFirstName",
73 | LastName = "bLastName",
74 | Email = "c@b.cEmail",
75 | UserName = "d@b.cUserName",
76 | Approved = true
77 | });
78 |
79 | returnList.Add(new ApplicationUser
80 | {
81 | FirstName = "bFirstName",
82 | LastName = "cLastName",
83 | Email = "d@b.cEmail",
84 | UserName = "a@b.cUserName",
85 | Approved = false
86 | });
87 |
88 | returnList.Add(new ApplicationUser
89 | {
90 | FirstName = "cFirstName",
91 | LastName = "dLastName",
92 | Email = "a@b.cEmail",
93 | UserName = "b@b.cUserName",
94 | Approved = false
95 | });
96 |
97 | returnList.Add(new ApplicationUser
98 | {
99 | FirstName = "dFirstName",
100 | LastName = "aLastName",
101 | Email = "b@b.cEmail",
102 | UserName = "c@b.cUserName",
103 | Approved = false
104 | });
105 |
106 | return returnList;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/UserManagementReact/UserManagementReact.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | true
6 | Latest
7 | false
8 | ClientApp\
9 | $(DefaultItemExcludes);$(SpaRoot)node_modules\**
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 |
24 |
25 |
26 |
27 | all
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | %(DistFiles.Identity)
66 | PreserveNewest
67 | true
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/UserManagementReact/Pages/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Mvc.ViewEngines
2 | @using Microsoft.AspNetCore.Hosting
3 | @inject IWebHostEnvironment Environment
4 | @inject ICompositeViewEngine Engine
5 |
6 |
7 |
8 |
9 |
10 | @ViewData["Title"] - UserManagementReact
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
49 |
50 |
51 |
52 |
53 |
54 | @RenderBody()
55 |
56 |
57 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
74 |
80 |
81 |
82 |
83 | @RenderSection("Scripts", required: false)
84 |
85 |
86 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register () {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW (swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker (swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister () {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------