[Templating]Api authorization spa integration (dotnet/aspnetcore#7721)

[Templating] Adds authentication support for the SPA templates
* Adds a new -auth option to the Angular and React templates with None and Individual values.
* When using authentication it configures the server to use the ApiAuthorization support built for 3.0
* For angular it adds an ApiAuthorization Angular module that handles the authentication flows through 
   a Login and a Logout components and exposes an Http client interceptor, a routing guard and a Login 
   menu component for the main Angular application to integrate authentication into the app.
* For react it adds a Login and Logout components to handle the authentication flows, an 
  AuthorizeRoute component to protect access to other routes and a Login menu component to 
  integrate authentication into the application.

Commit migrated from dotnet/aspnetcore@ea97934127
This commit is contained in:
Javier Calvarro Nelson 2019-02-21 16:23:55 -08:00 коммит произвёл Artak
Родитель ef07ca3db8
Коммит 15aff40cc9
89 изменённых файлов: 9209 добавлений и 3006 удалений

Просмотреть файл

@ -17,6 +17,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="${MicrosoftAspNetCoreMvcNewtonsoftJsonPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="${MicrosoftAspNetCoreSpaServicesExtensionsPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="${MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' == 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -9,12 +9,28 @@
<IsShippingPackage>true</IsShippingPackage>
</PropertyGroup>
<PropertyGroup>
<!-- Lists the versions of dependencies not built in this repo. Packages produced from this repo should be listed as a PackageVersionVariableReference. -->
<GeneratedContentProperties>
MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion);
MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion);
MicrosoftEntityFrameworkCoreToolsPackageVersion=$(MicrosoftEntityFrameworkCoreToolsPackageVersion);
MicrosoftExtensionsHostingPackageVersion=$(MicrosoftExtensionsHostingPackageVersion);
MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);
</GeneratedContentProperties>
</PropertyGroup>
<ItemGroup>
<!-- These projects product packages that the templates depend on. See GenerateContent.targets -->
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Mvc\Mvc.NewtonsoftJson\src\Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj" />
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Middleware\SpaServices.Extensions\src\Microsoft.AspNetCore.SpaServices.Extensions.csproj" />
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Identity\ApiAuthorization.IdentityServer\src\Microsoft.AspNetCore.ApiAuthorization.IdentityServer.csproj" />
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Identity\EntityFrameworkCore\src\Microsoft.AspNetCore.Identity.EntityFrameworkCore.csproj" />
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Identity\UI\src\Microsoft.AspNetCore.Identity.UI.csproj" />
<PackageVersionVariableReference Include="$(RepositoryRoot)src\Middleware\Diagnostics.EntityFrameworkCore\src\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj" />
</ItemGroup>
<ItemGroup>
<GeneratedContent Include="Angular-CSharp.csproj.in" OutputPath="content/Angular-CSharp/Company.WebApplication1.csproj" />
<GeneratedContent Include="React-CSharp.csproj.in" OutputPath="content/React-CSharp/Company.WebApplication1.csproj" />

Просмотреть файл

@ -14,6 +14,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="${MicrosoftAspNetCoreMvcNewtonsoftJsonPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="${MicrosoftAspNetCoreSpaServicesExtensionsPackageVersion}" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="${MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' == 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,13 +1,12 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"UseLocalDB": {
"longName": "use-local-db"
},
"Framework": {
"longName": "framework"
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
},
"HttpPort": {
"isHidden": true
},
@ -18,9 +17,19 @@
"longName": "exclude-launch-settings",
"shortName": ""
},
"UserSecretsId": {
"isHidden": true
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
},
"NoHttps": {
"longName": "no-https",
"shortName": ""
}
}
}
},
"usageExamples": [
"--auth Individual"
]
}

Просмотреть файл

@ -23,22 +23,88 @@
"exclude": [
".template.config/**"
],
"copyOnly": [
"wwwroot/**"
],
"modifiers": [
{
"condition": "(!IndividualLocalAuth)",
"exclude": [
"Pages/Shared/_LoginPartial.cshtml",
"Data/**",
"Models/**",
"ClientApp/src/api-authorization/**",
"Controllers/OidcConfigurationController.cs"
]
},
{
"condition": "(!IndividualLocalAuth || UseLocalDB)",
"exclude": [
"app.db"
]
},
{
"condition": "(ExcludeLaunchSettings)",
"exclude": [
"Properties/launchSettings.json"
]
},
{
"condition": "(IndividualLocalAuth && UseLocalDB)",
"rename": {
"Data/SQLServer/": "Data/Migrations/"
},
"exclude": [
"Data/SQLite/**"
]
},
{
"condition": "(IndividualLocalAuth && !UseLocalDB)",
"rename": {
"Data/SQLite/": "Data/Migrations/"
},
"exclude": [
"Data/SQLServer/**"
]
}
]
}
],
"symbols": {
"auth": {
"type": "parameter",
"datatype": "choice",
"choices": [
{
"choice": "None",
"description": "No authentication"
},
{
"choice": "Individual",
"description": "Individual authentication"
}
],
"defaultValue": "None",
"description": "The type of authentication to use"
},
"UserSecretsId": {
"type": "parameter",
"datatype": "string",
"replaces": "aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"defaultValue": "aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"description": "The ID to use for secrets (use with OrgReadAccess or Individual auth)."
},
"ExcludeLaunchSettings": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to exclude launchSettings.json from the generated template."
"description": "Whether to exclude launchSettings.json in the generated template."
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
},
"HttpPort": {
"type": "parameter",
@ -61,7 +127,7 @@
"HttpsPort": {
"type": "parameter",
"datatype": "integer",
"description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)."
"description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if IndividualLocalAuth is used)."
},
"HttpsPortGenerated": {
"type": "generated",
@ -80,6 +146,30 @@
},
"replaces": "44300"
},
"IndividualLocalAuth": {
"type": "computed",
"value": "(auth == \"Individual\")"
},
"NoAuth": {
"type": "computed",
"value": "(!(IndividualLocalAuth))"
},
"RequiresHttps": {
"type": "computed",
"value": "(IndividualLocalAuth || !NoHttps)"
},
"NoHttps": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
},
"UseLocalDB": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified."
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
@ -96,18 +186,6 @@
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
},
"NoHttps": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
}
},
"tags": {

5633
src/content/Angular-CSharp/ClientApp/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -12,42 +12,43 @@
},
"private": true,
"dependencies": {
"@angular/animations": "6.1.10",
"@angular/common": "6.1.10",
"@angular/compiler": "6.1.10",
"@angular/core": "6.1.10",
"@angular/forms": "6.1.10",
"@angular/http": "6.1.10",
"@angular/platform-browser": "6.1.10",
"@angular/platform-browser-dynamic": "6.1.10",
"@angular/platform-server": "6.1.10",
"@angular/router": "6.1.10",
"@nguniversal/module-map-ngfactory-loader": "6.0.0",
"core-js": "^2.5.4",
"rxjs": "^6.0.0",
"zone.js": "^0.8.26",
"@angular/animations": "7.2.5",
"@angular/common": "7.2.5",
"@angular/compiler": "7.2.5",
"@angular/core": "7.2.5",
"@angular/forms": "7.2.5",
"@angular/http": "7.2.5",
"@angular/platform-browser": "7.2.5",
"@angular/platform-browser-dynamic": "7.2.5",
"@angular/platform-server": "7.2.5",
"@angular/router": "7.2.5",
"@nguniversal/module-map-ngfactory-loader": "7.1.0",
"core-js": "^2.6.5",
"rxjs": "^6.4.0",
"zone.js": "^0.8.29",
"aspnet-prerendering": "^3.0.1",
"bootstrap": "^4.1.3",
"bootstrap": "^4.3.1",
"jquery": "3.3.1",
"oidc-client": "^1.6.1",
"popper.js": "^1.14.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.6.0",
"@angular/cli": "~6.0.0",
"@angular/compiler-cli": "6.1.10",
"@angular/language-service": "^6.0.0",
"@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "~4.2.1",
"jasmine-core": "~2.99.1",
"@angular-devkit/build-angular": "~0.13.2",
"@angular/cli": "~7.3.2",
"@angular/compiler-cli": "7.2.5",
"@angular/language-service": "^7.2.5",
"@types/jasmine": "~3.3.9",
"@types/jasminewd2": "~2.0.6",
"@types/node": "~11.9.4",
"codelyzer": "~4.5.0",
"jasmine-core": "~3.3.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "^3.0.0",
"karma": "^4.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~1.4.2",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"typescript": "~2.7.2"
"karma-coverage-istanbul-reporter": "~2.0.5",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"typescript": "~3.2.4"
},
"optionalDependencies": {
"node-sass": "^4.9.3",

Просмотреть файл

@ -0,0 +1,81 @@
export const ApplicationName = 'Company.WebApplication1';
export const ReturnUrlType = 'returnUrl';
export const QueryParameterNames = {
ReturnUrl: ReturnUrlType,
Message: 'message'
};
export const LogoutActions = {
LogoutCallback: 'logout-callback',
Logout: 'logout',
LoggedOut: 'logged-out'
};
export const LoginActions = {
Login: 'login',
LoginCallback: 'login-callback',
LoginFailed: 'login-failed',
Profile: 'profile',
Register: 'register'
};
let applicationPaths: ApplicationPathsType = {
DefaultLoginRedirectPath: '/',
ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,
Login: `authentication/${LoginActions.Login}`,
LoginFailed: `authentication/${LoginActions.LoginFailed}`,
LoginCallback: `authentication/${LoginActions.LoginCallback}`,
Register: `authentication/${LoginActions.Register}`,
Profile: `authentication/${LoginActions.Profile}`,
LogOut: `authentication/${LogoutActions.Logout}`,
LoggedOut: `authentication/${LogoutActions.LoggedOut}`,
LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`,
LoginPathComponents: [],
LoginFailedPathComponents: [],
LoginCallbackPathComponents: [],
RegisterPathComponents: [],
ProfilePathComponents: [],
LogOutPathComponents: [],
LoggedOutPathComponents: [],
LogOutCallbackPathComponents: [],
IdentityRegisterPath: '/Identity/Account/Register',
IdentityManagePath: '/Identity/Account/Manage'
};
applicationPaths = {
...applicationPaths,
LoginPathComponents: applicationPaths.Login.split('/'),
LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'),
RegisterPathComponents: applicationPaths.Register.split('/'),
ProfilePathComponents: applicationPaths.Profile.split('/'),
LogOutPathComponents: applicationPaths.LogOut.split('/'),
LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'),
LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/')
};
interface ApplicationPathsType {
readonly DefaultLoginRedirectPath: string;
readonly ApiAuthorizationClientConfigurationUrl: string;
readonly Login: string;
readonly LoginFailed: string;
readonly LoginCallback: string;
readonly Register: string;
readonly Profile: string;
readonly LogOut: string;
readonly LoggedOut: string;
readonly LogOutCallback: string;
readonly LoginPathComponents: string [];
readonly LoginFailedPathComponents: string [];
readonly LoginCallbackPathComponents: string [];
readonly RegisterPathComponents: string [];
readonly ProfilePathComponents: string [];
readonly LogOutPathComponents: string [];
readonly LoggedOutPathComponents: string [];
readonly LogOutCallbackPathComponents: string [];
readonly IdentityRegisterPath: string;
readonly IdentityManagePath: string;
}
export const ApplicationPaths: ApplicationPathsType = applicationPaths;

Просмотреть файл

@ -0,0 +1,13 @@
import { ApiAuthorizationModule } from './api-authorization.module';
describe('ApiAuthorizationModule', () => {
let apiAuthorizationModule: ApiAuthorizationModule;
beforeEach(() => {
apiAuthorizationModule = new ApiAuthorizationModule();
});
it('should create an instance', () => {
expect(apiAuthorizationModule).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginMenuComponent } from './login-menu/login-menu.component';
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';
import { RouterModule } from '@angular/router';
import { ApplicationPaths } from './api-authorization.constants';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
CommonModule,
HttpClientModule,
RouterModule.forChild(
[
{ path: ApplicationPaths.Register, component: LoginComponent },
{ path: ApplicationPaths.Profile, component: LoginComponent },
{ path: ApplicationPaths.Login, component: LoginComponent },
{ path: ApplicationPaths.LoginFailed, component: LoginComponent },
{ path: ApplicationPaths.LoginCallback, component: LoginComponent },
{ path: ApplicationPaths.LogOut, component: LogoutComponent },
{ path: ApplicationPaths.LoggedOut, component: LogoutComponent },
{ path: ApplicationPaths.LogOutCallback, component: LogoutComponent }
]
)
],
declarations: [LoginMenuComponent, LoginComponent, LogoutComponent],
exports: [LoginMenuComponent, LoginComponent, LogoutComponent]
})
export class ApiAuthorizationModule { }

Просмотреть файл

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthorizeGuard } from './authorize.guard';
describe('AuthorizeGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthorizeGuard]
});
});
it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => {
expect(guard).toBeTruthy();
}));
});

Просмотреть файл

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthorizeService } from './authorize.service';
import { tap } from 'rxjs/operators';
import { ApplicationPaths, QueryParameterNames } from './api-authorization.constants';
@Injectable({
providedIn: 'root'
})
export class AuthorizeGuard implements CanActivate {
constructor(private authorize: AuthorizeService, private router: Router) {
}
canActivate(
_next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authorize.isAuthenticated()
.pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state)));
}
private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) {
if (!isAuthenticated) {
this.router.navigate(ApplicationPaths.LoginPathComponents, {
queryParams: {
[QueryParameterNames.ReturnUrl]: state.url
}
});
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthorizeInterceptor } from './authorize.interceptor';
describe('AuthorizeInterceptor', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthorizeInterceptor]
});
});
it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => {
expect(service).toBeTruthy();
}));
});

Просмотреть файл

@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthorizeService } from './authorize.service';
import { mergeMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthorizeInterceptor implements HttpInterceptor {
constructor(private authorize: AuthorizeService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authorize.getAccessToken()
.pipe(mergeMap(token => this.processRequestWithToken(token, req, next)));
}
// Checks if there is an access_token available in the authorize service
// and adds it to the request in case it's targeted at the same origin as the
// single page application.
private processRequestWithToken(token: string, req: HttpRequest<any>, next: HttpHandler) {
if (!!token && this.isSameOriginUrl(req)) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(req);
}
private isSameOriginUrl(req: any) {
// It's an absolute url with the same origin.
if (req.url.startsWith(`${window.location.origin}/`)) {
return true;
}
// It's a protocol relative url with the same origin.
// For example: //www.example.com/api/Products
if (req.url.startsWith(`//${window.location.host}/`)) {
return true;
}
// It's a relative url like /api/Products
if (/^\/[^\/].*/.test(req.url)) {
return true;
}
// It's an absolute or protocol relative url that
// doesn't have the same origin.
return false;
}
}

Просмотреть файл

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AuthorizeService } from './authorize.service';
describe('AuthorizeService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AuthorizeService]
});
});
it('should be created', inject([AuthorizeService], (service: AuthorizeService) => {
expect(service).toBeTruthy();
}));
});

Просмотреть файл

@ -0,0 +1,324 @@
import { Injectable } from '@angular/core';
import { User, UserManager, WebStorageStateStore } from 'oidc-client';
import { BehaviorSubject, concat, from, Observable } from 'rxjs';
import { filter, map, mergeMap, take, tap } from 'rxjs/operators';
import { ApplicationPaths, ApplicationName } from './api-authorization.constants';
export type IAuthenticationResult =
SuccessAuthenticationResult |
FailureAuthenticationResult |
RedirectAuthenticationResult;
export interface SuccessAuthenticationResult {
status: AuthenticationResultStatus.Success;
state: any;
}
export interface FailureAuthenticationResult {
status: AuthenticationResultStatus.Fail;
message: string;
}
export interface RedirectAuthenticationResult {
status: AuthenticationResultStatus.Redirect;
redirectUrl: string;
}
export enum AuthenticationResultStatus {
Success,
Redirect,
Fail
}
export interface IUser {
name: string;
}
// Private interfaces
enum LoginMode {
Silent,
PopUp,
Redirect
}
interface IAuthenticationState {
mode: LoginMode;
userState?: any;
}
@Injectable({
providedIn: 'root'
})
export class AuthorizeService {
private userManager: UserManager;
private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null);
public isAuthenticated(): Observable<boolean> {
return this.getUser().pipe(map(u => !!u));
}
public getUser(): Observable<IUser | null> {
return concat(
this.userSubject.pipe(take(1), filter(u => !!u)),
this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),
this.userSubject.asObservable());
}
public getAccessToken(): Observable<string> {
return from(this.ensureUserManagerInitialized())
.pipe(mergeMap(() => from(this.userManager.getUser())),
map(user => user && user.access_token));
}
// We try to authenticate the user in three different ways:
// 1) We try to see if we can authenticate the user silently. This happens
// when the user is already logged in on the IdP and is done using a hidden iframe
// on the client.
// 2) We try to authenticate the user using a PopUp Window. This might fail if there is a
// Pop-Up blocker or the user has disabled PopUps.
// 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional
// redirect flow.
public async signIn(state: any): Promise<IAuthenticationResult> {
await this.ensureUserManagerInitialized();
let user: User = null;
try {
user = await this.userManager.signinSilent(this.createArguments(LoginMode.Silent));
this.userSubject.next(user.profile);
return this.success(state);
} catch (silentError) {
// User might not be authenticated, fallback to popup authentication
console.log('Silent authentication error: ', silentError);
try {
user = await this.userManager.signinPopup(this.createArguments(LoginMode.PopUp));
this.userSubject.next(user.profile);
return this.success(state);
} catch (popupError) {
if (popupError.message === 'Popup window closed') {
// The user explicitly cancelled the login action by closing an opened popup.
return this.error('The user closed the window.');
}
console.log('Popup authentication error: ', popupError);
// PopUps might be blocked by the user, fallback to redirect
try {
const signInRequest = await this.userManager.createSigninRequest(
this.createArguments(LoginMode.Redirect, state));
return this.redirect(signInRequest.url);
} catch (redirectError) {
console.log('Redirect authentication error: ', redirectError);
return this.error(redirectError);
}
}
}
}
// We are receiving a callback from the IdP. This code can be running in 3 situations:
// 1) As a hidden iframe started by a silent login on signIn (above). The code in the main
// browser window will close the iframe after returning from signInSilent.
// 2) As a PopUp window started by a pop-up login on signIn (above). The code in the main
// browser window will close the pop-up window after returning from signInPopUp
// 3) On the main browser window when the IdP redirects back to the app. We will process
// the response and redirect to the return url or display an error message.
public async completeSignIn(url: string): Promise<IAuthenticationResult> {
await this.ensureUserManagerInitialized();
let response;
try {
response = await this.getSignInResponse(url);
if (!!response.error) {
return this.error(`${response.error}: ${response.error_description}`);
}
} catch (processSignInResponseError) {
if (processSignInResponseError.error === 'login_required') {
// This error is thrown by the underlying oidc client when it tries to log in
// the user silently as in case 1 defined above and the IdP requires the user
// to enter credentials. We let the user manager handle the response to notify
// the main window.
response = processSignInResponseError;
} else {
console.log('There was an error processing the sign-in response: ', processSignInResponseError);
return this.error('There was an error processing the sign-in response.');
}
}
const authenticationState = response.state as IAuthenticationState;
const mode = authenticationState.mode;
switch (mode) {
case LoginMode.Silent:
try {
await this.userManager.signinSilentCallback(url);
return this.success(undefined);
} catch (silentCallbackError) {
console.log('Silent callback authentication error: ', silentCallbackError);
return this.error('Silent callback authentication error');
}
case LoginMode.PopUp:
try {
await this.userManager.signinPopupCallback(url);
return this.success(undefined);
} catch (popupCallbackError) {
console.log('Popup callback authentication error: ', popupCallbackError);
return this.error('Popup callback authentication error.');
}
case LoginMode.Redirect:
try {
const user = await this.userManager.signinRedirectCallback(url);
this.userSubject.next(user.profile);
return this.success(response.state.userState);
} catch (redirectCallbackError) {
console.log('Redirect callback authentication error: ', redirectCallbackError);
return this.error('Redirect callback authentication error.');
}
default:
throw new Error(`Invalid login mode '${mode}'.`);
}
}
// We try to sign out the user in two different ways:
// 1) We try to do a sign-out using a PopUp Window. This might fail if there is a
// Pop-Up blocker or the user has disabled PopUps.
// 2) If the method above fails, we redirect the browser to the IdP to perform a traditional
// post logout redirect flow.
public async signOut(state: any): Promise<IAuthenticationResult> {
await this.ensureUserManagerInitialized();
try {
await this.userManager.signoutPopup(this.createArguments(LoginMode.PopUp));
this.userSubject.next(null);
return this.success(state);
} catch (popupSignOutError) {
console.log('Popup signout error: ', popupSignOutError);
try {
const signInRequest = await this.userManager.createSignoutRequest(
this.createArguments(LoginMode.Redirect, state));
return this.redirect(signInRequest.url);
} catch (redirectSignOutError) {
console.log('Redirect signout error: ', popupSignOutError);
return this.error(redirectSignOutError);
}
}
}
// We are receiving a callback from the IdP. This code can be running in 2 situations:
// 1) As a PopUp window started by a pop-up login on signOut (above). The code in the main
// browser window will close the pop-up window after returning from signOutPopUp
// 2) On the main browser window when the IdP redirects back to the app. We will process
// the response and redirect to the logged-out url or display an error message.
public async completeSignOut(url: string): Promise<IAuthenticationResult> {
await this.ensureUserManagerInitialized();
let response;
try {
response = await await this.getSignOutResponse(url);
} catch (processSignOutResponseError) {
console.log('There was an error processing the sign-out response: ', processSignOutResponseError);
response = processSignOutResponseError;
}
if (!!response.error) {
return this.error(`${response.error}: ${response.error_description}`);
}
const authenticationState = response.state as IAuthenticationState;
const mode = (authenticationState && authenticationState.mode) ||
!!window.opener ? LoginMode.PopUp : LoginMode.Redirect;
switch (mode) {
case LoginMode.PopUp:
try {
await this.userManager.signoutPopupCallback(url);
return this.success(response.state && response.state.userState);
} catch (popupCallbackError) {
console.log('Popup signout callback error: ', popupCallbackError);
return this.error('Popup signout callback error');
}
case LoginMode.Redirect:
try {
await this.userManager.signoutRedirectCallback(url);
this.userSubject.next(null);
return this.success(response.state.userState);
} catch (redirectCallbackError) {
console.log('Redirect signout callback error: ', redirectCallbackError);
return this.error('Redirect signout callback error');
}
default:
throw new Error(`Invalid LoginMode '${mode}'.`);
}
}
private async getSignInResponse(url: string) {
const keys = await this.userManager.settings.stateStore.getAllKeys();
const states = keys.map(key => ({ key, state: this.userManager.settings.stateStore.get(key) }));
for (const state of states) {
state.state = await state.state;
}
try {
const response = await this.userManager.processSigninResponse(url);
return response;
} finally {
for (const state of states) {
await this.userManager.settings.stateStore.set(state.key, state.state);
}
}
}
private async getSignOutResponse(url: string) {
const keys = await this.userManager.settings.stateStore.getAllKeys();
const states = keys.map(key => ({ key, state: this.userManager.settings.stateStore.get(key) }));
for (const state of states) {
state.state = await state.state;
}
try {
const response = await this.userManager.processSignoutResponse(url);
return response;
} finally {
for (const state of states) {
await this.userManager.settings.stateStore.set(state.key, state.state);
}
}
}
private createArguments(mode: LoginMode, state?: any): any {
if (mode !== LoginMode.Silent) {
return { data: { mode, userState: state } };
} else {
return { data: { mode, userState: state }, redirect_uri: this.userManager.settings.redirect_uri };
}
}
private error(message: string): IAuthenticationResult {
return { status: AuthenticationResultStatus.Fail, message };
}
private success(state: any): IAuthenticationResult {
return { status: AuthenticationResultStatus.Success, state };
}
private redirect(redirectUrl: string): IAuthenticationResult {
return { status: AuthenticationResultStatus.Redirect, redirectUrl };
}
private async ensureUserManagerInitialized(): Promise<void> {
if (this.userManager !== undefined) {
return;
}
const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);
if (!response.ok) {
throw new Error(`Could not load settings for '${ApplicationName}'`);
}
const settings: any = await response.json();
settings.automaticSilentRenew = true;
settings.includeIdTokenInSilentRenew = true;
settings.userStore = new WebStorageStateStore({
prefix: ApplicationName
});
this.userManager = new UserManager(settings);
}
private getUserFromStorage(): Observable<IUser> {
return from(this.ensureUserManagerInitialized())
.pipe(
mergeMap(() => this.userManager.getUser()),
map(u => u && u.profile));
}
}

Просмотреть файл

@ -0,0 +1,16 @@
<ul class="navbar-nav" *ngIf="isAuthenticated | async">
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]='["/authentication/profile"]' title="Manage">Hello {{ userName | async }}</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]='["/authentication/logout"]' [state]='{ local: true }' title="Logout">Logout</a>
</li>
</ul>
<ul class="navbar-nav" *ngIf="!(isAuthenticated | async)">
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]='["/authentication/register"]'>Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]='["/authentication/login"]'>Login</a>
</li>
</ul>

Просмотреть файл

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginMenuComponent } from './login-menu.component';
describe('LoginMenuComponent', () => {
let component: LoginMenuComponent;
let fixture: ComponentFixture<LoginMenuComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginMenuComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { AuthorizeService } from '../authorize.service';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Component({
selector: 'app-login-menu',
templateUrl: './login-menu.component.html',
styleUrls: ['./login-menu.component.css']
})
export class LoginMenuComponent implements OnInit {
public isAuthenticated: Observable<boolean>;
public userName: Observable<string>;
constructor(private authorizeService: AuthorizeService) { }
ngOnInit() {
this.isAuthenticated = this.authorizeService.isAuthenticated();
this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));
}
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1 @@
<p>{{ message | async }}</p>

Просмотреть файл

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,132 @@
import { Component, OnInit } from '@angular/core';
import { AuthorizeService, AuthenticationResultStatus } from '../authorize.service';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';
// The main responsibility of this component is to handle the user's login process.
// This is the starting point for the login process. Any component that needs to authenticate
// a user can simply perform a redirect to this component with a returnUrl query parameter and
// let the component perform the login and return back to the return url.
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
private message = new BehaviorSubject<string>(null);
constructor(
private authorizeService: AuthorizeService,
private activatedRoute: ActivatedRoute,
private router: Router) { }
async ngOnInit() {
const action = this.activatedRoute.snapshot.url[1];
switch (action.path) {
case LoginActions.Login:
await this.login(this.getReturnUrl());
break;
case LoginActions.LoginCallback:
await this.processLoginCallback();
break;
case LoginActions.LoginFailed:
const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);
this.message.next(message);
break;
case LoginActions.Profile:
this.redirectToProfile();
break;
case LoginActions.Register:
this.redirectToRegister();
break;
default:
throw new Error(`Invalid action '${action}'`);
}
}
private async login(returnUrl: string): Promise<void> {
const state: INavigationState = { returnUrl };
const result = await this.authorizeService.signIn(state);
this.message.next(undefined);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// We replace the location here so that in case the user hits the back
// arrow from within the login page he doesn't get into an infinite
// redirect loop.
window.location.replace(result.redirectUrl);
break;
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(returnUrl);
break;
case AuthenticationResultStatus.Fail:
await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {
queryParams: { [QueryParameterNames.Message]: result.message }
});
break;
default:
throw new Error(`Invalid status result ${(result as any).status}.`);
}
}
private async processLoginCallback(): Promise<void> {
const url = window.location.href;
const result = await this.authorizeService.completeSignIn(url);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// There should not be any redirects as completeSignIn never redirects.
throw new Error('Should not redirect.');
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(this.getReturnUrl(result.state));
break;
case AuthenticationResultStatus.Fail:
this.message.next(result.message);
break;
}
}
private redirectToRegister(): any {
this.redirectToApiAuthorizationPath(
`${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`);
}
private redirectToProfile(): void {
this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);
}
private async navigateToReturnUrl(returnUrl: string) {
// It's important that we do a replace here so that we remove the callback uri with the
// fragment containing the tokens from the browser history.
await this.router.navigateByUrl(returnUrl, {
replaceUrl: true
});
}
private getReturnUrl(state?: INavigationState): string {
const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;
// If the url is comming from the query string, check that is either
// a relative url or an absolute url
if (fromQuery &&
!(fromQuery.startsWith(`${window.location.origin}/`) ||
/\/[^\/].*/.test(fromQuery))) {
// This is an extra check to prevent open redirects.
throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');
}
return (state && state.returnUrl) ||
fromQuery ||
ApplicationPaths.DefaultLoginRedirectPath;
}
private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {
// It's important that we do a replace here so that when the user hits the back arrow on the
// browser he gets sent back to where it was on the app instead of to an endpoint on this
// component.
const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;
window.location.replace(redirectUrl);
}
}
interface INavigationState {
[ReturnUrlType]: string;
}

Просмотреть файл

Просмотреть файл

@ -0,0 +1 @@
<p>{{ message | async }}</p>

Просмотреть файл

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LogoutComponent } from './logout.component';
describe('LogoutComponent', () => {
let component: LogoutComponent;
let fixture: ComponentFixture<LogoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LogoutComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LogoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,117 @@
import { Component, OnInit } from '@angular/core';
import { AuthenticationResultStatus, AuthorizeService } from '../authorize.service';
import { BehaviorSubject } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';
// The main responsibility of this component is to handle the user's logout process.
// This is the starting point for the logout process, which is usually initiated when a
// user clicks on the logout button on the LoginMenu component.
@Component({
selector: 'app-logout',
templateUrl: './logout.component.html',
styleUrls: ['./logout.component.css']
})
export class LogoutComponent implements OnInit {
private message = new BehaviorSubject<string>(null);
constructor(
private authorizeService: AuthorizeService,
private activatedRoute: ActivatedRoute,
private router: Router) { }
async ngOnInit() {
const action = this.activatedRoute.snapshot.url[1];
switch (action.path) {
case LogoutActions.Logout:
if (!!window.history.state.local) {
await this.logout(this.getReturnUrl());
} else {
// This prevents regular links to <app>/authentication/logout from triggering a logout
this.message.next('The logout was not initiated from within the page.');
}
break;
case LogoutActions.LogoutCallback:
await this.processLogoutCallback();
break;
case LogoutActions.LoggedOut:
this.message.next('You successfully logged out!');
break;
default:
throw new Error(`Invalid action '${action}'`);
}
}
private async logout(returnUrl: string): Promise<void> {
const state: INavigationState = { returnUrl };
const isauthenticated = await this.authorizeService.isAuthenticated().pipe(
take(1)
).toPromise();
if (isauthenticated) {
const result = await this.authorizeService.signOut(state);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// We replace the location here so that in case the user hits the back
// arrow from within the IdP he doesn't get into an infinite redirect loop.
window.location.replace(result.redirectUrl);
break;
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(returnUrl);
break;
case AuthenticationResultStatus.Fail:
this.message.next(result.message);
break;
default:
throw new Error('Invalid authentication result status.');
}
} else {
this.message.next('You successfully logged out!');
}
}
private async processLogoutCallback(): Promise<void> {
const url = window.location.href;
const result = await this.authorizeService.completeSignOut(url);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// There should not be any redirects as the only time completeAuthentication finishes
// is when we are doing a redirect sign in flow.
throw new Error('Should not redirect.');
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(this.getReturnUrl(result.state));
break;
case AuthenticationResultStatus.Fail:
this.message.next(result.message);
break;
default:
throw new Error('Invalid authentication result status.');
}
}
private async navigateToReturnUrl(returnUrl: string) {
await this.router.navigateByUrl(returnUrl, {
replaceUrl: true
});
}
private getReturnUrl(state?: INavigationState): string {
const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;
// If the url is comming from the query string, check that is either
// a relative url or an absolute url
if (fromQuery &&
!(fromQuery.startsWith(`${window.location.origin}/`) ||
/\/[^\/].*/.test(fromQuery))) {
// This is an extra check to prevent open redirects.
throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');
}
return (state && state.returnUrl) ||
fromQuery ||
ApplicationPaths.LoggedOut;
}
}
interface INavigationState {
[ReturnUrlType]: string;
}

Просмотреть файл

@ -1,7 +1,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
@ -9,6 +9,11 @@ import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
////#if (IndividualLocalAuth)
import { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';
import { AuthorizeGuard } from 'src/api-authorization/authorize.guard';
import { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';
////#endif
@NgModule({
declarations: [
@ -22,13 +27,26 @@ import { FetchDataComponent } from './fetch-data/fetch-data.component';
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
////#if (IndividualLocalAuth)
ApiAuthorizationModule,
////#endif
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
////#if (IndividualLocalAuth)
{ path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
////#else
{ path: 'fetch-data', component: FetchDataComponent },
////#endif
])
],
////#if (IndividualLocalAuth)
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true }
],
////#else
providers: [],
////#endif
bootstrap: [AppComponent]
})
export class AppModule { }

Просмотреть файл

@ -7,6 +7,9 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='{"show": isExpanded}'>
<!--#if (IndividualLocalAuth) -->
<app-login-menu></app-login-menu>
<!--#endif -->
<ul class="navbar-nav flex-grow">
<li class="nav-item" [routerLinkActive]='["link-active"]' [routerLinkActiveOptions]='{ exact: true }'>
<a class="nav-link text-dark" [routerLink]='["/"]'>Home</a>

Просмотреть файл

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
namespace Company.WebApplication1.Controllers
{
public class OidcConfigurationController : Controller
{
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider)
{
ClientRequestParametersProvider = clientRequestParametersProvider;
}
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
[HttpGet("_configuration/{clientId}")]
public IActionResult GetClientRequestParameters([FromRoute]string clientId)
{
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
return Ok(parameters);
}
}
}

Просмотреть файл

@ -3,9 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Authorization;
#endif
namespace Company.WebApplication1.Controllers
{
#if (IndividualLocalAuth)
[Authorize]
#endif
[Route("api/[controller]")]
public class SampleDataController : Controller
{

Просмотреть файл

@ -0,0 +1,21 @@
using Company.WebApplication1.Models;
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Company.WebApplication1.Data
{
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
}
}

Просмотреть файл

@ -0,0 +1,304 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,277 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Company.WebApplication1.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_UserCode",
table: "DeviceCodes",
column: "UserCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

Просмотреть файл

@ -0,0 +1,302 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,297 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1");
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,274 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Company.WebApplication1.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_UserCode",
table: "DeviceCodes",
column: "UserCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

Просмотреть файл

@ -0,0 +1,295 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1");
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Company.WebApplication1.Models
{
public class ApplicationUser : IdentityUser
{
}
}

Просмотреть файл

@ -0,0 +1,27 @@
@using Microsoft.AspNetCore.Identity
@using Company.WebApplication1.Models;
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

Просмотреть файл

@ -4,7 +4,7 @@
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8080",
//#if(NoHttps)
//#if(!RequiresHttps)
"sslPort": 0
//#else
"sslPort": 44300
@ -22,7 +22,7 @@
"Company.WebApplication1": {
"commandName": "Project",
"launchBrowser": true,
//#if(NoHttps)
//#if(!RequiresHttps)
"applicationUrl": "http://localhost:5000",
//#else
"applicationUrl": "https://localhost:5001;http://localhost:5000",
@ -32,4 +32,4 @@
}
}
}
}
}

Просмотреть файл

@ -1,10 +1,22 @@
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Authentication;
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
#if (!NoHttps)
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
#endif
#if (RequiresHttps)
using Microsoft.AspNetCore.HttpsPolicy;
#endif
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.AngularCli;
#if (IndividualLocalAuth)
using Microsoft.EntityFrameworkCore;
using Company.WebApplication1.Data;
using Company.WebApplication1.Models;
#endif
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -23,6 +35,26 @@ namespace Company.WebApplication1
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
#if (IndividualLocalAuth)
services.AddDbContext<ApplicationDbContext>(options =>
#if (UseLocalDB)
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
services.AddDefaultIdentity<ApplicationUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
#endif
services.AddMvc()
.AddNewtonsoftJson();
@ -39,11 +71,14 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{
app.UseExceptionHandler("/Error");
#if (!NoHttps)
#if (RequiresHttps)
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
@ -55,6 +90,11 @@ namespace Company.WebApplication1
#endif
app.UseStaticFiles();
app.UseSpaStaticFiles();
#if (IndividualLocalAuth)
app.UseAuthentication();
app.UseIdentityServer();
#endif
app.UseMvc(routes =>
{

Двоичные данные
src/content/Angular-CSharp/app.db Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -5,5 +5,14 @@
"System": "Information",
"Microsoft": "Information"
}
}
////#if (IndividualLocalAuth)
// },
// "IdentityServer": {
// "Key": {
// "Type": "Development"
// }
// }
////#else
// }
////#endif
}

Просмотреть файл

@ -1,8 +1,26 @@
{
////#if (IndividualLocalAuth)
// "ConnectionStrings": {
////#if (UseLocalDB)
// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
////#else
// "DefaultConnection": "DataSource=app.db"
////#endif
// },
////#endif
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
"LogLevel": {
"Default": "Warning"
}
},
////#if (IndividualLocalAuth)
// "IdentityServer": {
// "Clients": {
// "Company.WebApplication1": {
// "Profile": "IdentityServerSPA"
// }
// }
// },
////#endif
"AllowedHosts": "*"
}

Просмотреть файл

@ -1,13 +1,12 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"UseLocalDB": {
"longName": "use-local-db"
},
"Framework": {
"longName": "framework"
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
},
"HttpPort": {
"isHidden": true
},
@ -18,9 +17,19 @@
"longName": "exclude-launch-settings",
"shortName": ""
},
"UserSecretsId": {
"isHidden": true
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
},
"NoHttps": {
"longName": "no-https",
"shortName": ""
}
}
}
},
"usageExamples": [
"--auth Individual"
]
}

Просмотреть файл

@ -23,23 +23,89 @@
"exclude": [
".template.config/**"
],
"copyOnly": [
"wwwroot/**"
],
"modifiers": [
{
"condition": "(!IndividualLocalAuth)",
"exclude": [
"Pages/Shared/_LoginPartial.cshtml",
"Data/**",
"Models/**",
"ClientApp/src/components/api-authorization/**",
"Controllers/OidcConfigurationController.cs"
]
},
{
"condition": "(!IndividualLocalAuth || UseLocalDB)",
"exclude": [
"app.db"
]
},
{
"condition": "(ExcludeLaunchSettings)",
"exclude": [
"Properties/launchSettings.json"
]
},
{
"condition": "(IndividualLocalAuth && UseLocalDB)",
"rename": {
"Data/SQLServer/": "Data/Migrations/"
},
"exclude": [
"Data/SQLite/**"
]
},
{
"condition": "(IndividualLocalAuth && !UseLocalDB)",
"rename": {
"Data/SQLite/": "Data/Migrations/"
},
"exclude": [
"Data/SQLServer/**"
]
}
]
}
],
"symbols": {
"auth": {
"type": "parameter",
"datatype": "choice",
"choices": [
{
"choice": "None",
"description": "No authentication"
},
{
"choice": "Individual",
"description": "Individual authentication"
}
],
"defaultValue": "None",
"description": "The type of authentication to use"
},
"UserSecretsId": {
"type": "parameter",
"datatype": "string",
"replaces": "aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"defaultValue": "aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502",
"description": "The ID to use for secrets (use with OrgReadAccess or Individual auth)."
},
"ExcludeLaunchSettings": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to exclude launchSettings.json from the generated template."
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
},
"HttpPort": {
"type": "parameter",
"datatype": "integer",
@ -61,7 +127,7 @@
"HttpsPort": {
"type": "parameter",
"datatype": "integer",
"description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)."
"description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if IndividualLocalAuth is used)."
},
"HttpsPortGenerated": {
"type": "generated",
@ -80,6 +146,30 @@
},
"replaces": "44300"
},
"IndividualLocalAuth": {
"type": "computed",
"value": "(auth == \"Individual\")"
},
"NoAuth": {
"type": "computed",
"value": "(!(IndividualLocalAuth))"
},
"RequiresHttps": {
"type": "computed",
"value": "(IndividualLocalAuth || !NoHttps)"
},
"NoHttps": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
},
"UseLocalDB": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified."
},
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
@ -96,20 +186,9 @@
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
},
"NoHttps": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
}
},
"tags": {
"language": "C#",
"type": "project"

52
src/content/React-CSharp/ClientApp/package-lock.json сгенерированный
Просмотреть файл

@ -1016,6 +1016,31 @@
"babel-types": "^6.24.1"
}
},
"babel-polyfill": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"optional": true,
"requires": {
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
"regenerator-runtime": "^0.10.5"
},
"dependencies": {
"core-js": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==",
"optional": true
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
"optional": true
}
}
},
"babel-preset-env": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz",
@ -4085,7 +4110,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -4450,7 +4476,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -4498,6 +4525,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4536,11 +4564,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},
@ -6068,6 +6098,11 @@
"verror": "1.10.0"
}
},
"jsrsasign": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz",
"integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY="
},
"jsx-ast-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz",
@ -6882,6 +6917,15 @@
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
},
"oidc-client": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.6.1.tgz",
"integrity": "sha512-buA9G0hlFjUwxoL/xuHunBtTgoICtJSojnZtATqMliUTKxPYAzHprOQ85Lj0hgF+Zv8lI/ViqaNFDG0Z5KCUKA==",
"requires": {
"babel-polyfill": ">=6.9.1",
"jsrsasign": "^8.0.12"
}
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",

Просмотреть файл

@ -6,6 +6,7 @@
"bootstrap": "^4.1.3",
"jquery": "3.3.1",
"merge": "^1.2.1",
"oidc-client": "^1.6.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-router-bootstrap": "^0.24.4",

Просмотреть файл

@ -4,6 +4,11 @@ import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
////#if (IndividualLocalAuth)
import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
import ApiAuthorizationRoutes from './components/api-authorization/ApiAuthorizationRoutes'
import { ApplicationPaths } from './components/api-authorization/ApiAuthorizationConstants';
////#endif
export default class App extends Component {
static displayName = App.name;
@ -13,7 +18,13 @@ export default class App extends Component {
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
////#if (!IndividualLocalAuth)
<Route path='/fetch-data' component={FetchData} />
////#endif
////#if (IndividualLocalAuth)
<AuthorizeRoute path='/fetch-data' component={FetchData} />
<Route path={ApplicationPaths.ApiAuthorizationPrefix} Component={ApiAuthorizationRoutes} />
////#endif
</Layout>
);
}

Просмотреть файл

@ -1,22 +0,0 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
it('renders without crashing', () => {
const storeFake = (state: any) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});
const store = storeFake({});
ReactDOM.render(
<Provider store={store}>
<MemoryRouter>
<App/>
</MemoryRouter>
</Provider>, document.createElement('div'));
});

Просмотреть файл

@ -1,14 +0,0 @@
import * as React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
export default () => (
<Layout>
<Route exact path='/' component={Home}/>
<Route path='/counter' component={Counter} />
<Route path='/fetch-data/:startDateIndex?' component={FetchData} />
</Layout>
);

Просмотреть файл

@ -1,35 +0,0 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { ApplicationState } from '../store';
import * as CounterStore from '../store/Counter';
type CounterProps =
CounterStore.CounterState &
typeof CounterStore.actionCreators &
RouteComponentProps<{}>;
class Counter extends React.PureComponent<CounterProps> {
public render() {
return (
<React.Fragment>
<h1>Counter</h1>
<p>This is a simple example of a React component.</p>
<p>Current count: <strong>{this.props.count}</strong></p>
<button type="button"
className="btn btn-primary btn-lg"
onClick={() => { this.props.increment(); }}>
Increment
</button>
</React.Fragment>
);
}
};
export default connect(
(state: ApplicationState) => state.counter,
CounterStore.actionCreators
)(Counter);

Просмотреть файл

@ -1,20 +1,21 @@
import React, { Component } from 'react';
////#if (IndividualLocalAuth)
import authService from './api-authorization/AuthorizeService'
////#endif
export class FetchData extends Component {
static displayName = FetchData.name;
constructor (props) {
constructor(props) {
super(props);
this.state = { forecasts: [], loading: true };
fetch('api/SampleData/WeatherForecasts')
.then(response => response.json())
.then(data => {
this.setState({ forecasts: data, loading: false });
});
}
static renderForecastsTable (forecasts) {
componentDidMount() {
this.populateWeatherData();
}
static renderForecastsTable(forecasts) {
return (
<table className='table table-striped'>
<thead>
@ -39,7 +40,7 @@ export class FetchData extends Component {
);
}
render () {
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: FetchData.renderForecastsTable(this.state.forecasts);
@ -52,4 +53,19 @@ export class FetchData extends Component {
</div>
);
}
async populateWeatherData() {
////#if (IndividualLocalAuth)
const token = authService.getAccessToken();
const response = await fetch('api/SampleData/WeatherForecasts', {
headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
this.setState({ forecasts: data, loading: false });
////#else
const response = await fetch('api/SampleData/WeatherForecasts');
const data = await response.json();
this.setState({ forecasts: data, loading: false });
////#endif
}
}

Просмотреть файл

@ -1,84 +0,0 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { Link } from 'react-router-dom';
import { ApplicationState } from '../store';
import * as WeatherForecastsStore from '../store/WeatherForecasts';
// At runtime, Redux will merge together...
type WeatherForecastProps =
WeatherForecastsStore.WeatherForecastsState // ... state we've requested from the Redux store
& typeof WeatherForecastsStore.actionCreators // ... plus action creators we've requested
& RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
class FetchData extends React.PureComponent<WeatherForecastProps> {
// This method is called when the component is first added to the document
public componentDidMount() {
this.ensureDataFetched();
}
// This method is called when the route parameters change
public componentDidUpdate() {
this.ensureDataFetched();
}
public render() {
return (
<React.Fragment>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{ this.renderForecastsTable() }
{ this.renderPagination() }
</React.Fragment>
);
}
private ensureDataFetched() {
const startDateIndex = parseInt(this.props.match.params.startDateIndex, 10) || 0;
this.props.requestWeatherForecasts(startDateIndex);
}
private renderForecastsTable() {
return (
<table className='table table-striped'>
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{this.props.forecasts.map((forecast: WeatherForecastsStore.WeatherForecast) =>
<tr key={forecast.dateFormatted}>
<td>{forecast.dateFormatted}</td>
<td>{forecast.temperatureC}</td>
<td>{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
)}
</tbody>
</table>
);
}
private renderPagination() {
const prevStartDateIndex = (this.props.startDateIndex || 0) - 5;
const nextStartDateIndex = (this.props.startDateIndex || 0) + 5;
return (
<div className="d-flex justify-content-between">
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${prevStartDateIndex}`}>Previous</Link>
{this.props.isLoading && <span>Loading...</span>}
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${nextStartDateIndex}`}>Next</Link>
</div>
);
}
}
export default connect(
(state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props
WeatherForecastsStore.actionCreators // Selects which action creators are merged into the component's props
)(FetchData as any);

Просмотреть файл

@ -1,23 +0,0 @@
import * as React from 'react';
import { connect } from 'react-redux';
const Home = () => (
<div>
<h1>Hello, world!</h1>
<p>Welcome to your new single-page application, built with:</p>
<ul>
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
<li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
</ul>
<p>To help you get started, we've also set up:</p>
<ul>
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
<li><strong>Development server integration</strong>. In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files.</li>
</ul>
<p>The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>npm</code> commands such as <code>npm test</code> or <code>npm install</code>.</p>
</div>
);
export default connect()(Home);

Просмотреть файл

@ -1,12 +0,0 @@
import * as React from 'react';
import { Container } from 'reactstrap';
import NavMenu from './NavMenu';
export default (props: { children?: React.ReactNode }) => (
<React.Fragment>
<NavMenu/>
<Container>
{props.children}
</Container>
</React.Fragment>
);

Просмотреть файл

@ -1,6 +1,9 @@
import React, { Component } from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
////#if (IndividualLocalAuth)
import { LoginMenu } from './api-authorization/LoginMenu';
////#endif
import './NavMenu.css';
export class NavMenu extends Component {
@ -39,6 +42,10 @@ export class NavMenu extends Component {
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
////#if (IndividualLocalAuth)
<LoginMenu>
</LoginMenu>
////#endif
</ul>
</Collapse>
</Container>

Просмотреть файл

@ -1,42 +0,0 @@
import * as React from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';
export default class NavMenu extends React.PureComponent<{}, { isOpen: boolean }> {
public state = {
isOpen: false
};
public render() {
return (
<header>
<Navbar className="navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3" light>
<Container>
<NavbarBrand tag={Link} to="/">Company.WebApplication1</NavbarBrand>
<NavbarToggler onClick={this.toggle} className="mr-2"/>
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={this.state.isOpen} navbar>
<ul className="navbar-nav flex-grow">
<NavItem>
<NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
</ul>
</Collapse>
</Container>
</Navbar>
</header>
);
}
private toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
}

Просмотреть файл

@ -0,0 +1,38 @@
export const ApplicationName = 'Company.WebApplication1';
export const QueryParameterNames = {
ReturnUrl: 'returnUrl',
Message: 'message'
};
export const LogoutActions = {
LogoutCallback: 'logout-callback',
Logout: 'logout',
LoggedOut: 'logged-out'
};
export const LoginActions = {
Login: 'login',
LoginCallback: 'login-callback',
LoginFailed: 'login-failed',
Profile: 'profile',
Register: 'register'
};
const prefix = '/authentication';
export const ApplicationPaths = {
DefaultLoginRedirectPath: '/',
ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,
ApiAuthorizationPrefix = prefix,
Login: `${prefix}/${LoginActions.Login}`,
LoginFailed: `${prefix}/${LoginActions.LoginFailed}`,
LoginCallback: `${prefix}/${LoginActions.LoginCallback}`,
Register: `${prefix}/${LoginActions.Register}`,
Profile: `${prefix}/${LoginActions.Profile}`,
LogOut: `${prefix}/${LogoutActions.Logout}`,
LoggedOut: `${prefix}/${LogoutActions.LoggedOut}`,
LogOutCallback: `${prefix}/${LogoutActions.LogoutCallback}`,
IdentityRegisterPath: '/Identity/Account/Register',
IdentityManagePath: '/Identity/Account/Manage'
};

Просмотреть файл

@ -0,0 +1,30 @@
import React, { Component, Fragment } from 'react';
import { Route } from 'react-router';
import { Login } from './components/api-authorization/Login'
import { Logout } from './components/api-authorization/Logout'
import { ApplicationPaths, LoginActions, LogoutActions } from './ApiAuthorizationConstants';
export default class ApiAuthorizationRoutes extends Component {
render () {
return(
<Fragment>
<Route path={ApplicationPaths.Login} render={() => loginAction(LoginActions.Login)} />
<Route path={ApplicationPaths.LoginFailed} render={() => loginAction(LoginActions.LoginFailed)} />
<Route path={ApplicationPaths.LoginCallback} render={() => loginAction(LoginActions.LoginCallback)} />
<Route path={ApplicationPaths.Profile} render={() => loginAction(LoginActions.Profile)} />
<Route path={ApplicationPaths.Register} render={() => loginAction(LoginActions.Register)} />
<Route path={ApplicationPaths.LogOut} render={() => logoutAction(LogoutActions.Logout)} />
<Route path={ApplicationPaths.LogOutCallback} render={() => logoutAction(LogoutActions.LogoutCallback)} />
<Route path={ApplicationPaths.LoggedOut} render={() => logoutAction(LogoutActions.LoggedOut)} />
</Fragment>);
}
}
function loginAction(name){
return (<Login action={name}></Login>);
}
function logoutAction(name) {
return (<Logout action={name}></Logout>);
}

Просмотреть файл

@ -0,0 +1,43 @@
import React from 'react'
import { Component } from 'react'
import { Route, Redirect } from 'react-router-dom'
import { ApplicationPaths, QueryParameterNames } from './ApiAuthorizationConstants'
import authService from './AuthorizeService'
export default class AuthorizeRoute extends Component {
constructor(props) {
super(props);
this.state = {
ready: false,
authenticated: false
};
}
componentDidMount() {
this.populateAuthenticationState();
}
render() {
const { ready, authenticated } = this.state;
const redirectUrl = `${ApplicationPaths.Login}?${QueryParameterNames.ReturnUrl}=${encodeURI(window.location.href)}`
if (!ready) {
return <div></div>;
} else {
const { component: Component, ...rest } = this.props;
return <Route {...rest}
render={(props) => {
if (authenticated) {
return <Component {...props} />
} else {
return <Redirect to={redirectUrl} />
}
}} />
}
}
async populateAuthenticationState() {
const authenticated = await authService.isAuthenticated();
this.setState({ ready: true, authenticated });
}
}

Просмотреть файл

@ -0,0 +1,323 @@
import { UserManager, WebStorageStateStore } from 'oidc-client';
import { ApplicationPaths, ApplicationName } from './ApiAuthorizationConstants';
export class AuthorizeService {
_callbacks = [];
_nextSubscriptionId = 0;
_user = null;
_isAuthenticated = false;
async isAuthenticated() {
const user = await this.getUser();
return !!user;
}
async getUser() {
if (this._user && this._user.profile) {
return this._user.profile;
}
await this.ensureUserManagerInitialized();
const user = await this.userManager.getUser();
return user && user.profile;
}
async getAccessToken() {
await this.ensureUserManagerInitialized();
const user = await this.userManager.getUser();
return user && user.access_token;
}
// We try to authenticate the user in three different ways:
// 1) We try to see if we can authenticate the user silently. This happens
// when the user is already logged in on the IdP and is done using a hidden iframe
// on the client.
// 2) We try to authenticate the user using a PopUp Window. This might fail if there is a
// Pop-Up blocker or the user has disabled PopUps.
// 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional
// redirect flow.
async signIn(state) {
await this.ensureUserManagerInitialized();
try {
const silentUser = await this.userManager.signinSilent(this.createArguments(LoginMode.Silent));
this.updateState(silentUser);
return this.success(state);
} catch (silentError) {
// User might not be authenticated, fallback to popup authentication
console.log("Silent authentication error: ", silentError);
try {
const popUpUser = await this.userManager.signinPopup(this.createArguments(LoginMode.PopUp));
this.updateState(popUpUser);
return this.success(state);
} catch (popUpError) {
if (popUpError.message === "Popup window closed") {
// The user explicitly cancelled the login action by closing an opened popup.
return this.error("The user closed the window.");
}
console.log("Popup authentication error: ", popUpError);
// PopUps might be blocked by the user, fallback to redirect
try {
const signInRequest = await this.userManager.createSigninRequest(
this.createArguments(LoginMode.Redirect, state));
return this.redirect(signInRequest.url);
} catch (redirectError) {
console.log("Redirect authentication error: ", redirectError);
return this.error(redirectError);
}
}
}
}
// We are receiving a callback from the IdP. This code can be running in 3 situations:
// 1) As a hidden iframe started by a silent login on signIn (above). The code in the main
// browser window will close the iframe after returning from signInSilent.
// 2) As a PopUp window started by a pop-up login on signIn (above). The code in the main
// browser window will close the pop-up window after returning from signInPopUp
// 3) On the main browser window when the IdP redirects back to the app. We will process
// the response and redirect to the return url or display an error message.
async completeSignIn(url) {
await this.ensureUserManagerInitialized();
let response = undefined;
try {
response = await this.getSignInResponse(url);
if (!!response.error) {
return this.error(`${response.error}: ${response.error_description}`);
}
} catch (processSignInResponseError) {
if (processSignInResponseError.error === "login_required") {
// This error is thrown by the underlying oidc client when it tries to log in
// the user silently as in case 1 defined above and the IdP requires the user
// to enter credentials. We let the user manager handle the response to notify
// the main window.
response = processSignInResponseError;
} else {
console.log("There was an error processing the sign-in response: ", processSignInResponseError);
return this.error("There was an error processing the sign-in response.");
}
}
const authenticationState = response.state;
const mode = authenticationState.mode;
switch (mode) {
case LoginMode.Silent:
try {
await this.userManager.signinSilentCallback(url);
return this.success(undefined);
} catch (silentCallbackError) {
console.log("Silent callback authentication error: ", silentCallbackError);
return this.error("Silent callback authentication error");
}
case LoginMode.PopUp:
try {
await this.userManager.signinPopupCallback(url);
return this.success(undefined);
} catch (popupCallbackError) {
console.log("Popup callback authentication error: ", popupCallbackError);
return this.error("Popup callback authentication error.");
}
case LoginMode.Redirect:
try {
let user = await this.userManager.signinRedirectCallback(url);
this.updateState(user);
return this.success(response.state.userState);
} catch (redirectCallbackError) {
console.log("Redirect callback authentication error: ", redirectCallbackError);
return this.error("Redirect callback authentication error.");
}
default:
throw new Error(`Invalid login mode '${mode}'.`);
}
}
// We try to sign out the user in two different ways:
// 1) We try to do a sign-out using a PopUp Window. This might fail if there is a
// Pop-Up blocker or the user has disabled PopUps.
// 2) If the method above fails, we redirect the browser to the IdP to perform a traditional
// post logout redirect flow.
async signOut(state) {
await this.ensureUserManagerInitialized();
try {
await this.userManager.signoutPopup(this.createArguments(LoginMode.PopUp));
this.updateState(undefined);
return this.success(state);
} catch (popupSignOutError) {
console.log("Popup signout error: ", popupSignOutError);
try {
const signOutRequest = await this.userManager.createSignoutRequest(
this.createArguments(LoginMode.Redirect, state));
return this.redirect(signOutRequest.url);
} catch (redirectSignOutError) {
console.log("Redirect signout error: ", redirectSignOutError);
return this.error(redirectSignOutError);
}
}
}
// We are receiving a callback from the IdP. This code can be running in 2 situations:
// 1) As a PopUp window started by a pop-up login on signOut (above). The code in the main
// browser window will close the pop-up window after returning from signOutPopUp
// 2) On the main browser window when the IdP redirects back to the app. We will process
// the response and redirect to the logged-out url or display an error message.
async completeSignOut(url) {
await this.ensureUserManagerInitialized();
let response = undefined;
try {
response = await await this.getSignOutResponse(url);
} catch (processSignOutResponseError) {
console.log("There was an error processing the sign-out response: ", processSignOutResponseError);
response = processSignOutResponseError;
}
if (!!response.error) {
return this.error(`${response.error}: ${response.error_description}`);
}
const authenticationState = response.state;
const mode = (authenticationState && authenticationState.mode) ||
!!window.opener ? LoginMode.PopUp : LoginMode.Redirect;
switch (mode) {
case LoginMode.PopUp:
try {
await this.userManager.signoutPopupCallback(url);
return this.success(response.state && response.state.userState);
} catch (popupCallbackError) {
console.log("Popup signout callback error: ", popupCallbackError);
return this.error("Popup signout callback error");
}
case LoginMode.Redirect:
try {
await this.userManager.signoutRedirectCallback(url);
this.updateState(undefined);
return this.success(response.state.userState);
} catch (redirectCallbackError) {
console.log("Redirect signout callback error: ", redirectCallbackError);
return this.error("Redirect signout callback error");
}
default:
throw new Error(`Invalid LoginMode '${mode}'.`);
}
}
updateState(user) {
this._user = user;
this._isAuthenticated = !!this._user;
this.notifySubscribers();
}
subscribe(callback) {
this._callbacks.push({ callback, subscription: this._nextSubscriptionId++ });
return this._nextSubscriptionId - 1;
}
unsubscribe(subscriptionId) {
const subscriptionIndex = this._callbacks
.map((element, index) => element.subscription === subscriptionId ? { found: true, index } : { found: false })
.filter(element => element.found === true);
if (subscriptionIndex.length !== 1) {
throw new Error(`Found an invalid number of subscriptions ${subscriptionIndex.length}`);
}
this._callbacks = this._callbacks.splice(subscriptionIndex[0].index, 1);
}
notifySubscribers() {
for (let i = 0; i < this._callbacks.length; i++) {
const callback = this._callbacks[i].callback;
callback();
}
}
async getSignInResponse(url) {
const keys = await this.userManager.settings.stateStore.getAllKeys();
const states = keys.map(key => ({ key, state: this.userManager.settings.stateStore.get(key) }));
for (const state of states) {
state.state = await state.state;
}
try {
const response = await this.userManager.processSigninResponse(url);
return response;
} finally {
for (const state of states) {
await this.userManager.settings.stateStore.set(state.key, state.state);
}
}
}
async getSignOutResponse(url) {
const keys = await this.userManager.settings.stateStore.getAllKeys();
const states = keys.map(key => ({ key, state: this.userManager.settings.stateStore.get(key) }));
for (const state of states) {
state.state = await state.state;
}
try {
const response = await this.userManager.processSignoutResponse(url);
return response;
} finally {
for (const state of states) {
await this.userManager.settings.stateStore.set(state.key, state.state);
}
}
}
createArguments(mode, state) {
if (mode !== LoginMode.Silent) {
return { data: { mode, userState: state } };
} else {
return { data: { mode, userState: state }, redirect_uri: this.userManager.settings.redirect_uri };
}
}
error(message) {
return { status: AuthenticationResultStatus.Fail, message };
}
success(state) {
return { status: AuthenticationResultStatus.Success, state };
}
redirect(redirectUrl) {
return { status: AuthenticationResultStatus.Redirect, redirectUrl };
}
async ensureUserManagerInitialized() {
if (this.userManager !== undefined) {
return;
}
let response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);
if (!response.ok) {
throw new Error(`Could not load settings for '${ApplicationName}'`);
}
let settings = await response.json();
settings.automaticSilentRenew = true;
settings.includeIdTokenInSilentRenew = true;
settings.userStore = new WebStorageStateStore({
prefix: ApplicationName
});
this.userManager = new UserManager(settings);
}
static get instance() { return authService }
}
const LoginMode = {
Silent: 'silent',
PopUp: 'popup',
Redirect: 'redirect'
}
const authService = new AuthorizeService();
export default authService;
export const AuthenticationResultStatus = {
Redirect: 'redirect',
Success: 'success',
Fail: 'fail'
};

Просмотреть файл

@ -0,0 +1,137 @@
import React from 'react'
import { Component } from 'react';
import authService from './AuthorizeService';
import { AuthenticationResultStatus } from './AuthorizeService';
import { LoginActions, QueryParameterNames, ApplicationPaths } from './ApiAuthorizationConstants';
// The main responsibility of this component is to handle the user's login process.
// This is the starting point for the login process. Any component that needs to authenticate
// a user can simply perform a redirect to this component with a returnUrl query parameter and
// let the component perform the login and return back to the return url.
export class Login extends Component {
constructor(props) {
super(props);
this.state = {
message: undefined
};
}
componentDidMount() {
const action = this.props.action;
switch (action) {
case LoginActions.Login:
this.login(this.getReturnUrl());
break;
case LoginActions.LoginCallback:
this.processLoginCallback();
break;
case LoginActions.LoginFailed:
const params = new URLSearchParams(window.location.search);
const error = params.get(QueryParameterNames.Message);
this.setState({ message: error });
break;
case LoginActions.Profile:
this.redirectToProfile();
break;
case LoginActions.Register:
this.redirectToRegister();
break;
default:
throw new Error(`Invalid action '${action}'`);
}
}
render() {
const action = this.props.action;
const { message } = this.state;
if (!!message) {
return <div>{message}</div>
} else {
switch (action) {
case LoginActions.Login:
return (<div>Processing login</div>);
case LoginActions.LoginCallback:
return (<div>Processing login callback</div>);
case LoginActions.Profile:
case LoginActions.Register:
return (<div></div>);
default:
throw new Error(`Invalid action '${action}'`);
}
}
}
async login(returnUrl) {
const state = { returnUrl };
const result = await authService.signIn(state);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// We replace the location here so that in case the user hits the back
// arrow from within the login page he doesn't get into an infinite
// redirect loop.
window.location.replace(result.redirectUrl);
break;
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(returnUrl);
break;
case AuthenticationResultStatus.Fail:
this.setState({ message: result.message });
break;
default:
throw new Error(`Invalid status result ${result.status}.`);
}
}
async processLoginCallback() {
const url = window.location.href;
const result = await authService.completeSignIn(url);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// There should not be any redirects as the only time completeSignIn finishes
// is when we are doing a redirect sign in flow.
throw new Error('Should not redirect.');
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(this.getReturnUrl(result.state));
break;
case AuthenticationResultStatus.Fail:
this.setState({ message: result.message });
break;
default:
throw new Error(`Invalid authentication result status '${result.status}'.`);
}
}
getReturnUrl(state) {
const params = new URLSearchParams(window.location.search);
const fromQuery = params.get(QueryParameterNames.ReturnUrl);
if (fromQuery && !fromQuery.startsWith(`${window.location.origin}/`)) {
// This is an extra check to prevent open redirects.
throw new Error("Invalid return url. The return url needs to have the same origin as the current page.")
}
return (state && state.returnUrl) || fromQuery || `${window.location.origin}/`;
}
redirectToRegister() {
this.redirectToApiAuthorizationPath(`${ApplicationPaths.IdentityRegisterPath}?${QueryParameterNames.ReturnUrl}=${encodeURI(ApplicationPaths.Login)}`);
}
redirectToProfile() {
this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);
}
redirectToApiAuthorizationPath(apiAuthorizationPath) {
const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;
// It's important that we do a replace here so that when the user hits the back arrow on the
// browser he gets sent back to where it was on the app instead of to an endpoint on this
// component.
window.location.replace(redirectUrl);
}
navigateToReturnUrl(returnUrl) {
// It's important that we do a replace here so that we remove the callback uri with the
// fragment containing the tokens from the browser history.
window.location.replace(returnUrl);
}
}

Просмотреть файл

@ -0,0 +1,69 @@
import React, { Component, Fragment } from 'react';
import { NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import authService from './AuthorizeService';
import { ApplicationPaths } from './ApiAuthorizationConstants';
export class LoginMenu extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
userName: null
};
}
componentDidMount() {
this._subscription = authService.subscribe(() => this.populateState());
this.populateState();
}
componentWillUnmount() {
authService.unsubscribe(this._subscription);
}
async populateState() {
const [isAuthenticated, user] = await Promise.all([authService.isAuthenticated(), authService.getUser()])
this.setState({
isAuthenticated,
userName: user && user.name
});
}
render() {
const { isAuthenticated, userName } = this.state;
if (!isAuthenticated) {
const registerPath = `${ApplicationPaths.Register}`;
const loginPath = `${ApplicationPaths.Login}`;
return this.anonymousView(registerPath, loginPath);
} else {
const profilePath = `${ApplicationPaths.Profile}`;
const logoutPath = { pathname: `${ApplicationPaths.LogOut}`, state: { local: true } };
return this.authenticatedView(userName, profilePath, logoutPath);
}
}
authenticatedView(userName, profilePath, logoutPath) {
return (<Fragment>
<NavItem>
<NavLink tag={Link} className="text-dark" to={profilePath}>Hello {userName}</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to={logoutPath}>Logout</NavLink>
</NavItem>
</Fragment>);
}
anonymousView(registerPath, loginPath) {
return (<Fragment>
<NavItem>
<NavLink tag={Link} className="text-dark" to={registerPath}>Register</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to={loginPath}>Login</NavLink>
</NavItem>
</Fragment>);
}
}

Просмотреть файл

@ -0,0 +1,131 @@
import React from 'react'
import { Component } from 'react';
import authService from './AuthorizeService';
import { AuthenticationResultStatus } from './AuthorizeService';
import { QueryParameterNames, LogoutActions, ApplicationPaths } from './ApiAuthorizationConstants';
// The main responsibility of this component is to handle the user's logout process.
// This is the starting point for the logout process, which is usually initiated when a
// user clicks on the logout button on the LoginMenu component.
export class Logout extends Component {
constructor(props) {
super(props);
this.state = {
message: undefined,
isReady: false,
authenticated: false
};
}
componentDidMount() {
const action = this.props.action;
switch (action) {
case LogoutActions.Logout:
if (!!window.history.state.state.local) {
this.logout(this.getReturnUrl());
} else {
// This prevents regular links to <app>/authentication/logout from triggering a logout
this.setState({ isReady: true, message: "The logout was not initiated from within the page." });
}
break;
case LogoutActions.LogoutCallback:
this.processLogoutCallback();
break;
case LogoutActions.LoggedOut:
this.setState({ isReady: true, message: "You successfully logged out!" });
break;
default:
throw new Error(`Invalid action '${action}'`);
}
this.populateAuthenticationState();
}
render() {
const { isReady, message } = this.state;
if (!isReady) {
return <div></div>
}
if (!!message) {
return (<div>{message}</div>);
} else {
const action = this.props.action;
switch (action) {
case LogoutActions.Logout:
return (<div>Processing logout</div>);
case LogoutActions.LogoutCallback:
return (<div>Processing logout callback</div>);
case LogoutActions.LoggedOut:
return (<div>{message}</div>);
default:
throw new Error(`Invalid action '${action}'`);
}
}
}
async logout(returnUrl) {
const state = { returnUrl };
const isauthenticated = await authService.isAuthenticated();
if (isauthenticated) {
const result = await authService.signOut(state);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// We replace the location here so that in case the user hits the back
// arrow from within the IdP he doesn't get into an infinite redirect loop.
window.location.replace(result.redirectUrl);
break;
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(returnUrl);
break;
case AuthenticationResultStatus.Fail:
this.setState({ message: result.message });
break;
default:
throw new Error("Invalid authentication result status.");
}
} else {
this.setState({ message: "You successfully logged out!" });
}
}
async processLogoutCallback() {
const url = window.location.href;
const result = await authService.completeSignOut(url);
switch (result.status) {
case AuthenticationResultStatus.Redirect:
// There should not be any redirects as the only time completeAuthentication finishes
// is when we are doing a redirect sign in flow.
throw new Error('Should not redirect.');
case AuthenticationResultStatus.Success:
await this.navigateToReturnUrl(this.getReturnUrl(result.state));
break;
case AuthenticationResultStatus.Fail:
this.setState({ message: result.message });
break;
default:
throw new Error("Invalid authentication result status.");
}
}
async populateAuthenticationState() {
const authenticated = await authService.isAuthenticated();
this.setState({ isReady: true, authenticated });
}
getReturnUrl(state) {
const params = new URLSearchParams(window.location.search);
const fromQuery = params.get(QueryParameterNames.ReturnUrl);
if (fromQuery && !fromQuery.startsWith(`${window.location.origin}/`)) {
// This is an extra check to prevent open redirects.
throw new Error("Invalid return url. The return url needs to have the same origin as the current page.")
}
return (state && state.returnUrl) ||
fromQuery ||
`${window.location.origin}${ApplicationPaths.LoggedOut}`;
}
navigateToReturnUrl(returnUrl) {
return window.location.replace(returnUrl);
}
}

Просмотреть файл

@ -1,27 +0,0 @@
import 'bootstrap/dist/css/bootstrap.css';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { createBrowserHistory } from 'history';
import configureStore from './store/configureStore';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href') as string;
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const store = configureStore(history);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'));
registerServiceWorker();

Просмотреть файл

@ -1 +0,0 @@
/// <reference types="react-scripts" />

Просмотреть файл

@ -1,105 +0,0 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const url = process.env.PUBLIC_URL as string;
const publicUrl = new URL(url, window.location.toString());
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing as ServiceWorker;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (response.status === 404 || (contentType && contentType.indexOf('javascript') === -1)) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.');
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

Просмотреть файл

@ -1,48 +0,0 @@
import { Action, Reducer } from 'redux';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface CounterState {
count: number;
}
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
export interface IncrementCountAction { type: 'INCREMENT_COUNT' }
export interface DecrementCountAction { type: 'DECREMENT_COUNT' }
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
export type KnownAction = IncrementCountAction | DecrementCountAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
increment: () => <IncrementCountAction>{ type: 'INCREMENT_COUNT' },
decrement: () => <DecrementCountAction>{ type: 'DECREMENT_COUNT' }
};
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
export const reducer: Reducer<CounterState> = (state: CounterState | undefined, incomingAction: Action): CounterState => {
if (state === undefined) {
return { count: 0 };
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'INCREMENT_COUNT':
return { count: state.count + 1 };
case 'DECREMENT_COUNT':
return { count: state.count - 1 };
default:
return state;
}
};

Просмотреть файл

@ -1,91 +0,0 @@
import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface WeatherForecastsState {
isLoading: boolean;
startDateIndex?: number;
forecasts: WeatherForecast[];
}
export interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
interface RequestWeatherForecastsAction {
type: 'REQUEST_WEATHER_FORECASTS';
startDateIndex: number;
}
interface ReceiveWeatherForecastsAction {
type: 'RECEIVE_WEATHER_FORECASTS';
startDateIndex: number;
forecasts: WeatherForecast[];
}
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState();
if (appState && appState.weatherForecasts && startDateIndex !== appState.weatherForecasts.startDateIndex) {
fetch(`api/SampleData/WeatherForecasts?startDateIndex=${startDateIndex}`)
.then(response => response.json() as Promise<WeatherForecast[]>)
.then(data => {
dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data });
});
dispatch({ type: 'REQUEST_WEATHER_FORECASTS', startDateIndex: startDateIndex });
}
}
};
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: WeatherForecastsState = { forecasts: [], isLoading: false };
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState | undefined, incomingAction: Action): WeatherForecastsState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_WEATHER_FORECASTS':
return {
startDateIndex: action.startDateIndex,
forecasts: state.forecasts,
isLoading: true
};
case 'RECEIVE_WEATHER_FORECASTS':
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
// handle out-of-order responses.
if (action.startDateIndex === state.startDateIndex) {
return {
startDateIndex: action.startDateIndex,
forecasts: action.forecasts,
isLoading: false
};
}
break;
}
return state;
};

Просмотреть файл

@ -1,29 +0,0 @@
import { applyMiddleware, combineReducers, compose, createStore, Reducer } from 'redux';
import thunk from 'redux-thunk';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import { History } from 'history';
import { ApplicationState, reducers } from './';
export default function configureStore(history: History, initialState?: ApplicationState) {
const middleware = [
thunk,
routerMiddleware(history)
];
const rootReducer = combineReducers({
...reducers,
router: connectRouter(history)
});
const enhancers = [];
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) {
enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__());
}
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}

Просмотреть файл

@ -1,22 +0,0 @@
import * as WeatherForecasts from './WeatherForecasts';
import * as Counter from './Counter';
// The top-level state object
export interface ApplicationState {
counter: Counter.CounterState | undefined;
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined;
}
// Whenever an action is dispatched, Redux will update each top-level application state property using
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
// acts on the corresponding ApplicationState property type.
export const reducers = {
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer
};
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are
// correctly typed to match your store.
export interface AppThunkAction<TAction> {
(dispatch: (action: TAction) => void, getState: () => ApplicationState): void;
}

Просмотреть файл

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
namespace Company.WebApplication1.Controllers
{
public class OidcConfigurationController : Controller
{
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider)
{
ClientRequestParametersProvider = clientRequestParametersProvider;
}
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
[HttpGet("_configuration/{clientId}")]
public IActionResult GetClientRequestParameters([FromRoute]string clientId)
{
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
return Ok(parameters);
}
}
}

Просмотреть файл

@ -2,10 +2,16 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Authorization;
#endif
using Microsoft.AspNetCore.Mvc;
namespace Company.WebApplication1.Controllers
{
#if (IndividualLocalAuth)
[Authorize]
#endif
[Route("api/[controller]")]
public class SampleDataController : Controller
{

Просмотреть файл

@ -0,0 +1,21 @@
using Company.WebApplication1.Models;
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Company.WebApplication1.Data
{
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
}
}

Просмотреть файл

@ -0,0 +1,304 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,277 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Company.WebApplication1.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_UserCode",
table: "DeviceCodes",
column: "UserCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

Просмотреть файл

@ -0,0 +1,302 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,297 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1");
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,274 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Company.WebApplication1.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(maxLength: 200, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: false),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_UserCode",
table: "DeviceCodes",
column: "UserCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

Просмотреть файл

@ -0,0 +1,295 @@
// <auto-generated />
using System;
using Company.WebApplication1.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Company.WebApplication1.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0-preview.19080.1");
modelBuilder.Entity("Company.WebApplication1.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.ValueGeneratedOnAdd()
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Expiration")
.IsRequired();
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("UserCode")
.IsUnique();
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Company.WebApplication1.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Company.WebApplication1.Models
{
public class ApplicationUser : IdentityUser
{
}
}

Просмотреть файл

@ -0,0 +1,27 @@
@using Microsoft.AspNetCore.Identity
@using Company.WebApplication1.Models;
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

Просмотреть файл

@ -1,10 +1,22 @@
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Authentication;
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
#if (!NoHttps)
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
#endif
#if (RequiresHttps)
using Microsoft.AspNetCore.HttpsPolicy;
#endif
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
#if (IndividualLocalAuth)
using Microsoft.EntityFrameworkCore;
using Company.WebApplication1.Data;
using Company.WebApplication1.Models;
#endif
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -23,6 +35,26 @@ namespace Company.WebApplication1
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
#if (IndividualLocalAuth)
services.AddDbContext<ApplicationDbContext>(options =>
#if (UseLocalDB)
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
services.AddDefaultIdentity<ApplicationUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
#endif
services.AddMvc()
.AddNewtonsoftJson();
@ -39,11 +71,14 @@ namespace Company.WebApplication1
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
#if (IndividualLocalAuth)
app.UseDatabaseErrorPage();
#endif
}
else
{
app.UseExceptionHandler("/Error");
#if (!NoHttps)
#if (RequiresHttps)
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
@ -55,6 +90,11 @@ namespace Company.WebApplication1
#endif
app.UseStaticFiles();
app.UseSpaStaticFiles();
#if (IndividualLocalAuth)
app.UseAuthentication();
app.UseIdentityServer();
#endif
app.UseMvc(routes =>
{

Двоичные данные
src/content/React-CSharp/app.db Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -5,5 +5,14 @@
"System": "Information",
"Microsoft": "Information"
}
}
////#if (IndividualLocalAuth)
// },
// "IdentityServer": {
// "Key": {
// "Type": "Development"
// }
// }
////#else
// }
////#endif
}

Просмотреть файл

@ -1,8 +1,26 @@
{
////#if (IndividualLocalAuth)
// "ConnectionStrings": {
////#if (UseLocalDB)
// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Company.WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
////#else
// "DefaultConnection": "DataSource=app.db"
////#endif
// },
////#endif
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
"LogLevel": {
"Default": "Warning"
}
},
////#if (IndividualLocalAuth)
// "IdentityServer": {
// "Clients": {
// "Company.WebApplication1": {
// "Profile": "IdentityServerSPA"
// }
// }
// },
////#endif
"AllowedHosts": "*"
}