Merge branch 'dev' into stsrki/dev-blazor-server

pull/8074/head
Halil İbrahim Kalkan 5 years ago
commit 3dd275b468

@ -218,6 +218,22 @@
"ReIndexAllPosts": "Reindex All Posts",
"ReIndexAllPostsConfirmationMessage": "Are you sure you want to reindex all posts?",
"SuccessfullyReIndexAllPosts": "All posts have been successfully reindexed.",
"Permission:FullSearch": "Full text search"
"Permission:FullSearch": "Full text search",
"Menu:CliAnalytics": "Cli Analytics",
"TemplateName": "Template name",
"TemplateVersion": "Template version",
"DatabaseProvider": "Database provider",
"IsTiered": "Is Tiered",
"ProjectName": "Project name",
"Username": "User name",
"Tool": "Tool",
"Command": "Command",
"UiFramework": "Ui framework",
"Options": "Options",
"CliAnalytics": "Cli Analytics",
"Permission:CliAnalyticses": "Cli Analyticses",
"Permission:CliAnalytics": "Cli Analytics",
"Search": "Search",
"ClearFilter": "Clear filter"
}
}

@ -166,6 +166,22 @@
"DeleteCoverImageSuccessMessage": "Kapak fotoğrafı başarılı bir şekilde silinmiştir",
"ReIndexAllPosts": "Yeniden indeksle",
"ReIndexAllPostsConfirmationMessage": "Tüm gönderileri yeniden indeksleyeceğinizden emin misiniz?",
"SuccessfullyReIndexAllPosts": "Tüm gönderiler başarıyla yeniden indekslendi"
"SuccessfullyReIndexAllPosts": "Tüm gönderiler başarıyla yeniden indekslendi",
"Menu:CliAnalytics": "Cli Analitik",
"TemplateName": "Şablon ismi",
"TemplateVersion": "Şablon versiyon",
"DatabaseProvider": "Veritabanı sağlayıcı",
"IsTiered": "Katmanlı mı",
"ProjectName": "Proje ismi",
"Username": "Kullanıcı adı",
"Tool": "Araç",
"Command": "Komut",
"UiFramework": "Ui çerçevesi",
"Options": "Seçenekler",
"CliAnalytics": "Cli Analitik",
"Permission:CliAnalyticses": "Cli Analitikleri",
"Permission:CliAnalytics": "Cli Analitik",
"Search": "Ara",
"ClearFilter": "Filtreyi temizle"
}
}

@ -781,7 +781,7 @@ namespace IssueTracking.Issues
- 创建时间大于30天
- 最近30天没有评论
这个业务逻辑就被实现了仓储内部,当我们需要重用这个业务规则时就会出现问题.
这个业务逻辑就被实现了仓储内部,当我们需要重用这个业务规则时就会出现问题.
例如:我们需要再实体`Issue`上添加一个方法来判断是否非活动`bool IsInActive()`,以方便我们在`Issue`实例上获取.

@ -4,12 +4,15 @@ using System.Security.Claims;
using IdentityModel;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Security.Claims;
namespace Volo.Abp.IdentityServer
{
public class AbpClaimsService : DefaultClaimsService
{
protected readonly AbpClaimsServiceOptions Options;
private static readonly string[] AdditionalOptionalClaimNames =
{
AbpClaimTypes.TenantId,
@ -20,20 +23,21 @@ namespace Volo.Abp.IdentityServer
JwtClaimTypes.FamilyName,
};
public AbpClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger)
public AbpClaimsService(
IProfileService profile,
ILogger<DefaultClaimsService> logger,
IOptions<AbpClaimsServiceOptions> options)
: base(profile, logger)
{
Options = options.Value;
}
protected override IEnumerable<string> FilterRequestedClaimTypes(IEnumerable<string> claimTypes)
{
return base.FilterRequestedClaimTypes(claimTypes)
.Union(new []{
AbpClaimTypes.TenantId,
AbpClaimTypes.EditionId
});
.Union(Options.RequestedClaims);
}
protected override IEnumerable<Claim> GetOptionalClaims(ClaimsPrincipal subject)
{
return base.GetOptionalClaims(subject)
@ -52,4 +56,4 @@ namespace Volo.Abp.IdentityServer
}
}
}
}
}

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Volo.Abp.IdentityServer
{
public class AbpClaimsServiceOptions
{
public List<string> RequestedClaims { get; }
public AbpClaimsServiceOptions()
{
RequestedClaims = new List<string>();
}
}
}

@ -10,7 +10,6 @@ using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.AspNetIdentity;
using Volo.Abp.IdentityServer.ApiScopes;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.IdentityResources;
@ -19,6 +18,7 @@ using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.Security;
using Volo.Abp.Security.Claims;
using Volo.Abp.Validation;
using Volo.Abp.Threading;
@ -54,6 +54,14 @@ namespace Volo.Abp.IdentityServer
options.EtoMappings.Add<IdentityResource, IdentityResourceEto>(typeof(AbpIdentityServerDomainModule));
});
Configure<AbpClaimsServiceOptions>(options =>
{
options.RequestedClaims.AddRange(new []{
AbpClaimTypes.TenantId,
AbpClaimTypes.EditionId
});
});
AddIdentityServer(context.Services);
}

@ -24,7 +24,7 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
}
[UnitOfWork]
public async override Task GetProfileDataAsync(ProfileDataRequestContext context)
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
using (CurrentTenant.Change(context.Subject.FindTenantId()))
{
@ -33,7 +33,7 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
}
[UnitOfWork]
public async override Task IsActiveAsync(IsActiveContext context)
public override async Task IsActiveAsync(IsActiveContext context)
{
using (CurrentTenant.Change(context.Subject.FindTenantId()))
{

@ -184,6 +184,43 @@
}
}
},
"account": {
"projectType": "library",
"root": "packages/account",
"sourceRoot": "packages/account/src",
"prefix": "abp",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "packages/account/tsconfig.lib.json",
"project": "packages/account/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "packages/account/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"coverage": true,
"passWithNoTests": true
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"packages/account/tsconfig.lib.json",
"packages/account/tsconfig.spec.json"
],
"exclude": ["**/node_modules/**"]
}
}
}
},
"identity": {
"projectType": "library",
"root": "packages/identity",

@ -7,6 +7,10 @@ const routes: Routes = [
pathMatch: 'full',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule),
},
{
path: 'account',
loadChildren: () => import('@abp/ng.account').then(m => m.AccountModule.forLazy()),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),

@ -11,6 +11,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { InspectorModule } from '@ngneat/inspector';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
import { NgxsModule } from '@ngxs/store';
import { AccountConfigModule } from '@abp/ng.account/config';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -33,6 +34,7 @@ const INSPECTION_TOOLS = [
skipGetAppConfiguration: false,
}),
ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(),
TenantManagementConfigModule.forRoot(),
SettingManagementConfigModule.forRoot(),

@ -14,6 +14,6 @@ export class HomeComponent {
constructor(private oAuthService: OAuthService, private authService: AuthService) {}
login() {
this.authService.initLogin();
this.authService.navigateToLogin();
}
}

@ -12,10 +12,11 @@ export const environment = {
},
oAuthConfig: {
issuer: 'https://localhost:44305',
redirectUri: baseUrl,
clientId: 'MyProjectName_App',
responseType: 'code',
dummyClientSecret: '1q2w3e*',
scope: 'offline_access MyProjectName',
// responseType: 'code',
// redirectUri: baseUrl
},
apis: {
default: {

@ -0,0 +1,3 @@
<h1> @abp/ng.account </h1>
[docs.abp.io](https://docs.abp.io)

@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/account/config",
"lib": {
"entryFile": "src/public-api.ts"
}
}

@ -0,0 +1,12 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { ACCOUNT_ROUTE_PROVIDERS } from './providers/route.provider';
@NgModule()
export class AccountConfigModule {
static forRoot(): ModuleWithProviders<AccountConfigModule> {
return {
ngModule: AccountConfigModule,
providers: [ACCOUNT_ROUTE_PROVIDERS],
};
}
}

@ -0,0 +1,6 @@
export const enum eAccountRouteNames {
Account = 'AbpAccount::Menu:Account',
Login = 'AbpAccount::Login',
Register = 'AbpAccount::Register',
ManageProfile = 'AbpAccount::ManageYourProfile',
}

@ -0,0 +1,39 @@
import { eLayoutType, RoutesService } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { eAccountRouteNames } from '../enums/route-names';
export const ACCOUNT_ROUTE_PROVIDERS = [
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
];
export function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '/account',
name: eAccountRouteNames.Account,
invisible: true,
layout: eLayoutType.application,
order: 1,
},
{
path: '/account/login',
name: eAccountRouteNames.Login,
parentName: eAccountRouteNames.Account,
order: 1,
},
{
path: '/account/register',
name: eAccountRouteNames.Register,
parentName: eAccountRouteNames.Account,
order: 2,
},
{
path: '/account/manage-profile',
name: eAccountRouteNames.ManageProfile,
parentName: eAccountRouteNames.Account,
order: 3,
},
]);
};
}

@ -0,0 +1,3 @@
export * from './account-config.module';
export * from './enums';
export * from './providers';

@ -0,0 +1,6 @@
const jestConfig = require('../../jest.config');
module.exports = {
...jestConfig,
name: 'account',
};

@ -0,0 +1,8 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/account",
"lib": {
"entryFile": "src/public-api.ts"
},
"whitelistedNonPeerDependencies": ["@abp/ng.theme.shared"]
}

@ -0,0 +1,6 @@
module.exports = {
entryPoints: {
'.': {},
'./config': {}
},
};

@ -0,0 +1,16 @@
{
"name": "@abp/ng.account",
"version": "4.3.0",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "~3.3.2",
"tslib": "^2.0.0"
},
"publishConfig": {
"access": "public"
}
}

@ -0,0 +1,93 @@
import {
AuthGuard,
DynamicLayoutComponent,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { LoginComponent } from './components/login/login.component';
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
import { RegisterComponent } from './components/register/register.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { eAccountComponents } from './enums/components';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'login' },
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: '',
component: AuthWrapperComponent,
children: [
{
path: 'login',
component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard],
data: {
replaceableComponent: {
key: eAccountComponents.Login,
defaultComponent: LoginComponent,
} as ReplaceableComponents.RouteData<LoginComponent>,
},
},
{
path: 'register',
component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard],
data: {
replaceableComponent: {
key: eAccountComponents.Register,
defaultComponent: RegisterComponent,
} as ReplaceableComponents.RouteData<RegisterComponent>,
},
},
{
path: 'forgot-password',
component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard],
data: {
replaceableComponent: {
key: eAccountComponents.ForgotPassword,
defaultComponent: ForgotPasswordComponent,
} as ReplaceableComponents.RouteData<ForgotPasswordComponent>,
},
},
{
path: 'reset-password',
component: ReplaceableRouteContainerComponent,
canActivate: [AuthenticationFlowGuard],
data: {
replaceableComponent: {
key: eAccountComponents.ResetPassword,
defaultComponent: ResetPasswordComponent,
} as ReplaceableComponents.RouteData<ResetPasswordComponent>,
},
},
],
},
{
path: 'manage-profile',
component: ReplaceableRouteContainerComponent,
canActivate: [AuthGuard],
data: {
replaceableComponent: {
key: eAccountComponents.ManageProfile,
defaultComponent: ManageProfileComponent,
} as ReplaceableComponents.RouteData<ManageProfileComponent>,
},
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AccountRoutingModule {}

@ -0,0 +1,63 @@
import { CoreModule, LazyModuleFactory } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { AccountRoutingModule } from './account-routing.module';
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
import { ChangePasswordComponent } from './components/change-password/change-password.component';
import { LoginComponent } from './components/login/login.component';
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
import { PersonalSettingsComponent } from './components/personal-settings/personal-settings.component';
import { RegisterComponent } from './components/register/register.component';
import { TenantBoxComponent } from './components/tenant-box/tenant-box.component';
import { AccountConfigOptions } from './models/config-options';
import { ACCOUNT_CONFIG_OPTIONS } from './tokens/config-options.token';
import { accountConfigOptionsFactory } from './utils/factory-utils';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
const declarations = [
AuthWrapperComponent,
LoginComponent,
RegisterComponent,
TenantBoxComponent,
ChangePasswordComponent,
ManageProfileComponent,
PersonalSettingsComponent,
ForgotPasswordComponent,
ResetPasswordComponent,
];
@NgModule({
declarations: [...declarations],
imports: [
CoreModule,
AccountRoutingModule,
ThemeSharedModule,
NgbDropdownModule,
NgxValidateCoreModule,
],
exports: [...declarations],
})
export class AccountModule {
static forChild(options = {} as AccountConfigOptions): ModuleWithProviders<AccountModule> {
return {
ngModule: AccountModule,
providers: [
AuthenticationFlowGuard,
{ provide: ACCOUNT_CONFIG_OPTIONS, useValue: options },
{
provide: 'ACCOUNT_OPTIONS',
useFactory: accountConfigOptionsFactory,
deps: [ACCOUNT_CONFIG_OPTIONS],
},
],
};
}
static forLazy(options = {} as AccountConfigOptions): NgModuleFactory<AccountModule> {
return new LazyModuleFactory(AccountModule.forChild(options));
}
}

@ -0,0 +1,25 @@
<div class="row">
<div class="mx-auto col col-md-5">
<ng-container *ngIf="(isMultiTenancyEnabled$ | async) && multiTenancy.isTenantBoxVisible">
<abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box>
</ng-container>
<div class="abp-account-container">
<div
*ngIf="enableLocalLogin$ | async; else disableLocalLoginTemplate"
class="card mt-3 shadow-sm rounded"
>
<div class="card-body p-5">
<router-outlet></router-outlet>
</div>
</div>
</div>
</div>
</div>
<ng-template #disableLocalLoginTemplate>
<div class="alert alert-warning">
<strong>{{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}</strong>
{{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }}
</div>
</ng-template>

@ -0,0 +1,28 @@
import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { eAccountComponents } from '../../enums/components';
@Component({
selector: 'abp-auth-wrapper',
templateUrl: './auth-wrapper.component.html',
exportAs: 'abpAuthWrapper',
providers: [SubscriptionService],
})
export class AuthWrapperComponent {
isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled');
get enableLocalLogin$(): Observable<boolean> {
return this.configState
.getSetting$('Abp.Account.EnableLocalLogin')
.pipe(map(value => value?.toLowerCase() !== 'false'));
}
tenantBoxKey = eAccountComponents.TenantBox;
constructor(
public readonly multiTenancy: MultiTenancyService,
private configState: ConfigStateService,
) {}
}

@ -0,0 +1,48 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit>
<div *ngIf="!hideCurrentPassword" class="form-group">
<label for="current-password">{{
'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization
}}</label
><span> * </span
><input
type="password"
id="current-password"
class="form-control"
formControlName="password"
autofocus
autocomplete="current-password"
/>
</div>
<div class="form-group">
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label
><span> * </span
><input
type="password"
id="new-password"
class="form-control"
formControlName="newPassword"
autocomplete="new-password"
/>
</div>
<div class="form-group">
<label for="confirm-new-password">{{
'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization
}}</label
><span> * </span
><input
type="password"
id="confirm-new-password"
class="form-control"
formControlName="repeatNewPassword"
autocomplete="new-password"
/>
</div>
<abp-button
iconClass="fa fa-check"
buttonClass="btn btn-primary color-white"
buttonType="submit"
[loading]="inProgress"
[disabled]="form?.invalid"
>{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</form>

@ -0,0 +1,99 @@
import { Profile, ProfileService } from '@abp/ng.core';
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, Input, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { comparePasswords, Validation } from '@ngx-validate/core';
import { finalize } from 'rxjs/operators';
import snq from 'snq';
import { Account } from '../../models/account';
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
const { required } = Validators;
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
@Component({
selector: 'abp-change-password-form',
templateUrl: './change-password.component.html',
exportAs: 'abpChangePasswordForm',
})
export class ChangePasswordComponent
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs {
form: FormGroup;
inProgress: boolean;
hideCurrentPassword: boolean;
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
constructor(
private fb: FormBuilder,
private injector: Injector,
private toasterService: ToasterService,
private profileService: ProfileService,
private manageProfileState: ManageProfileStateService,
) {}
ngOnInit(): void {
this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword;
const passwordValidations = getPasswordValidators(this.injector);
this.form = this.fb.group(
{
password: ['', required],
newPassword: [
'',
{
validators: [required, ...passwordValidations],
},
],
repeatNewPassword: [
'',
{
validators: [required, ...passwordValidations],
},
],
},
{
validators: [comparePasswords(PASSWORD_FIELDS)],
},
);
if (this.hideCurrentPassword) this.form.removeControl('password');
}
onSubmit() {
if (this.form.invalid) return;
this.inProgress = true;
this.profileService
.changePassword({
...(!this.hideCurrentPassword && { currentPassword: this.form.get('password').value }),
newPassword: this.form.get('newPassword').value,
})
.pipe(finalize(() => (this.inProgress = false)))
.subscribe({
next: () => {
this.form.reset();
this.toasterService.success('AbpAccount::PasswordChangedMessage', '', {
life: 5000,
});
if (this.hideCurrentPassword) {
this.hideCurrentPassword = false;
this.form.addControl('password', new FormControl('', [required]));
}
},
error: err => {
this.toasterService.error(
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
);
},
});
}
}

@ -0,0 +1,40 @@
<h4>{{ 'AbpAccount::ForgotPassword' | abpLocalization }}</h4>
<form
*ngIf="!isEmailSent; else emailSentTemplate"
[formGroup]="form"
(ngSubmit)="onSubmit()"
validateOnSubmit
>
<p>{{ 'AbpAccount::SendPasswordResetLink_Information' | abpLocalization }}</p>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span>
<input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<abp-button
class="d-block"
buttonClass="mt-2 mb-3 btn btn-primary btn-block"
[loading]="inProgress"
buttonType="submit"
[disabled]="form?.invalid"
>
{{ 'AbpAccount::Submit' | abpLocalization }}
</abp-button>
<a routerLink="/account/login"
><i class="fa fa-long-arrow-left mr-1"></i>{{ 'AbpAccount::Login' | abpLocalization }}</a
>
</form>
<ng-template #emailSentTemplate>
<p>
{{ 'AbpAccount::PasswordResetMailSentMessage' | abpLocalization }}
</p>
<a routerLink="/account/login">
<button class="d-block mt-2 mb-3 btn btn-primary btn-block">
<i class="fa fa-long-arrow-left mr-1"></i>
{{ 'AbpAccount::BackToLogin' | abpLocalization }}
</button>
</a>
</ng-template>

@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { AccountService } from '../../proxy/account/account.service';
@Component({
selector: 'abp-forgot-password',
templateUrl: 'forgot-password.component.html',
})
export class ForgotPasswordComponent {
form: FormGroup;
inProgress: boolean;
isEmailSent = false;
constructor(private fb: FormBuilder, private accountService: AccountService) {
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
});
}
onSubmit() {
if (this.form.invalid) return;
this.inProgress = true;
this.accountService
.sendPasswordResetCode({ email: this.form.get('email').value, appName: 'Angular' })
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => {
this.isEmailSent = true;
});
}
}

@ -0,0 +1,8 @@
export * from './change-password/change-password.component';
export * from './forgot-password/forgot-password.component';
export * from './login/login.component';
export * from './manage-profile/manage-profile.component';
export * from './register/register.component';
export * from './personal-settings/personal-settings.component';
export * from './reset-password/reset-password.component';
export * from './tenant-box/tenant-box.component';

@ -0,0 +1,60 @@
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong *ngIf="isSelfRegistrationEnabled">
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register" queryParamsHandling="preserve">{{
'AbpAccount::Register' | abpLocalization
}}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<div class="form-group">
<label for="login-input-user-name-or-email-address">{{
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
}}</label>
<input
class="form-control"
type="text"
id="login-input-user-name-or-email-address"
formControlName="username"
autocomplete="username"
autofocus
/>
</div>
<div class="form-group">
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
<input
class="form-control"
type="password"
id="login-input-password"
formControlName="password"
autocomplete="current-password"
/>
</div>
<div class="row">
<div class="col">
<label class="custom-checkbox custom-control mb-2" for="login-input-remember-me">
<input
class="form-check-input"
type="checkbox"
id="login-input-remember-me"
formControlName="rememberMe"
/>
{{ 'AbpAccount::RememberMe' | abpLocalization }}
</label>
</div>
<div class="text-right col">
<a routerLink="/account/forgot-password">{{
'AbpAccount::ForgotPassword' | abpLocalization
}}</a>
</div>
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Login' | abpLocalization }}
</abp-button>
</form>

@ -0,0 +1,80 @@
import { ConfigStateService, AuthService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngxs/store';
import { throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import snq from 'snq';
import { eAccountComponents } from '../../enums/components';
import { getRedirectUrl } from '../../utils/auth-utils';
const { maxLength, required } = Validators;
@Component({
selector: 'abp-login',
templateUrl: './login.component.html',
})
export class LoginComponent implements OnInit {
form: FormGroup;
inProgress: boolean;
isSelfRegistrationEnabled = true;
authWrapperKey = eAccountComponents.AuthWrapper;
constructor(
protected injector: Injector,
protected fb: FormBuilder,
protected toasterService: ToasterService,
protected authService: AuthService,
protected configState: ConfigStateService,
) {}
ngOnInit() {
this.init();
this.buildForm();
}
protected init() {
this.isSelfRegistrationEnabled =
(
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') as string) || ''
).toLowerCase() !== 'false';
}
protected buildForm() {
this.form = this.fb.group({
username: ['', [required, maxLength(255)]],
password: ['', [required, maxLength(128)]],
rememberMe: [false],
});
}
onSubmit() {
if (this.form.invalid) return;
this.inProgress = true;
const { username, password, rememberMe } = this.form.value;
const redirectUrl = getRedirectUrl(this.injector);
this.authService
.login({ username, password, rememberMe, redirectUrl })
.pipe(
catchError(err => {
this.toasterService.error(
snq(() => err.error.error_description) ||
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
'Error',
{ life: 7000 },
);
return throwError(err);
}),
finalize(() => (this.inProgress = false)),
)
.subscribe();
}
}

@ -0,0 +1,62 @@
<div id="AbpContentToolbar"></div>
<div class="card border-0 shadow-sm min-h-400" [abpLoading]="!(profile$ | async)">
<div class="card-body">
<div class="row">
<div class="col-12 col-md-3">
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist">
<li
*ngIf="!hideChangePasswordTab && (profile$ | async)"
class="nav-item"
(click)="selectedTab = 0"
>
<a
class="nav-link"
[ngClass]="{ active: selectedTab === 0 }"
role="tab"
href="javascript:void(0)"
>{{ 'AbpUi::ChangePassword' | abpLocalization }}</a
>
</li>
<li class="nav-item mb-2" (click)="selectedTab = 1">
<a
class="nav-link"
[ngClass]="{ active: selectedTab === 1 }"
role="tab"
href="javascript:void(0)"
>{{ 'AbpAccount::PersonalSettings' | abpLocalization }}</a
>
</li>
</ul>
</div>
<div *ngIf="profile$ | async" class="col-12 col-md-9">
<div class="tab-content" *ngIf="selectedTab === 0" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<h4>
{{ 'AbpIdentity::ChangePassword' | abpLocalization }}
<hr />
</h4>
<abp-change-password-form
*abpReplaceableTemplate="{
componentKey: changePasswordKey
}"
></abp-change-password-form>
</div>
</div>
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]>
<div class="tab-pane active" role="tabpanel">
<h4>
{{ 'AbpIdentity::PersonalSettings' | abpLocalization }}
<hr />
</h4>
<abp-personal-settings-form
*abpReplaceableTemplate="{
componentKey: personalSettingsKey
}"
></abp-personal-settings-form>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,45 @@
import { Profile, ProfileService } from '@abp/ng.core';
import { fadeIn } from '@abp/ng.theme.shared';
import { transition, trigger, useAnimation } from '@angular/animations';
import { Component, OnInit } from '@angular/core';
import { eAccountComponents } from '../../enums/components';
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
@Component({
selector: 'abp-manage-profile',
templateUrl: './manage-profile.component.html',
animations: [trigger('fadeIn', [transition(':enter', useAnimation(fadeIn))])],
styles: [
`
.min-h-400 {
min-height: 400px;
}
`,
],
})
export class ManageProfileComponent implements OnInit {
selectedTab = 0;
changePasswordKey = eAccountComponents.ChangePassword;
personalSettingsKey = eAccountComponents.PersonalSettings;
profile$ = this.manageProfileState.getProfile$();
hideChangePasswordTab: boolean;
constructor(
protected profileService: ProfileService,
protected manageProfileState: ManageProfileStateService,
) {}
ngOnInit() {
this.profileService.get().subscribe(profile => {
this.manageProfileState.setProfile(profile);
if (profile.isExternal) {
this.hideChangePasswordTab = true;
this.selectedTab = 1;
}
});
}
}

@ -0,0 +1,46 @@
<form validateOnSubmit *ngIf="form" [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group">
<label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label
><span> * </span
><input
type="text"
id="username"
class="form-control"
formControlName="userName"
autofocus
(keydown.space)="$event.preventDefault()"
/>
</div>
<div class="row">
<div class="col col-md-6">
<div class="form-group">
<label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label
><input type="text" id="name" class="form-control" formControlName="name" />
</div>
</div>
<div class="col col-md-6">
<div class="form-group">
<label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label
><input type="text" id="surname" class="form-control" formControlName="surname" />
</div>
</div>
</div>
<div class="form-group">
<label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label
><span> * </span
><input type="text" id="email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" />
</div>
<abp-button
buttonType="submit"
iconClass="fa fa-check"
buttonClass="btn btn-primary color-white"
[loading]="inProgress"
[disabled]="form?.invalid"
>
{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</form>

@ -0,0 +1,58 @@
import { ProfileService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { Account } from '../../models/account';
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
const { maxLength, required, email } = Validators;
@Component({
selector: 'abp-personal-settings-form',
templateUrl: './personal-settings.component.html',
exportAs: 'abpPersonalSettingsForm',
})
export class PersonalSettingsComponent
implements
OnInit,
Account.PersonalSettingsComponentInputs,
Account.PersonalSettingsComponentOutputs {
form: FormGroup;
inProgress: boolean;
constructor(
private fb: FormBuilder,
private toasterService: ToasterService,
private profileService: ProfileService,
private manageProfileState: ManageProfileStateService,
) {}
ngOnInit() {
this.buildForm();
}
buildForm() {
const profile = this.manageProfileState.getProfile();
this.form = this.fb.group({
userName: [profile.userName, [required, maxLength(256)]],
email: [profile.email, [required, email, maxLength(256)]],
name: [profile.name || '', [maxLength(64)]],
surname: [profile.surname || '', [maxLength(64)]],
phoneNumber: [profile.phoneNumber || '', [maxLength(16)]],
});
}
submit() {
if (this.form.invalid) return;
this.inProgress = true;
this.profileService
.update(this.form.value)
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(profile => {
this.manageProfileState.setProfile(profile);
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
});
}
}

@ -0,0 +1,51 @@
<h4>{{ 'AbpAccount::Register' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/login">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</strong>
<form
*ngIf="isSelfRegistrationEnabled"
[formGroup]="form"
(ngSubmit)="onSubmit()"
validateOnSubmit
class="mt-4"
>
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span
><input
autofocus
type="text"
id="input-user-name"
class="form-control"
formControlName="username"
autocomplete="username"
/>
</div>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span
><input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span
><input
type="password"
id="input-password"
class="form-control"
formControlName="password"
autocomplete="current-password"
/>
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Register' | abpLocalization }}
</abp-button>
</form>

@ -0,0 +1,102 @@
import { AuthService, ConfigStateService } from '@abp/ng.core';
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { throwError } from 'rxjs';
import { catchError, finalize, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { eAccountComponents } from '../../enums/components';
import { AccountService } from '../../proxy/account/account.service';
import { RegisterDto } from '../../proxy/account/models';
import { getRedirectUrl } from '../../utils/auth-utils';
const { maxLength, required, email } = Validators;
@Component({
selector: 'abp-register',
templateUrl: './register.component.html',
})
export class RegisterComponent implements OnInit {
form: FormGroup;
inProgress: boolean;
isSelfRegistrationEnabled = true;
authWrapperKey = eAccountComponents.AuthWrapper;
constructor(
protected fb: FormBuilder,
protected accountService: AccountService,
protected configState: ConfigStateService,
protected toasterService: ToasterService,
protected authService: AuthService,
protected injector: Injector,
) {}
ngOnInit() {
this.init();
this.buildForm();
}
protected init() {
this.isSelfRegistrationEnabled =
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') || '').toLowerCase() !==
'false';
if (!this.isSelfRegistrationEnabled) {
this.toasterService.warn(
{
key: 'AbpAccount::SelfRegistrationDisabledMessage',
defaultValue: 'Self registration is disabled.',
},
null,
{ life: 10000 },
);
return;
}
}
protected buildForm() {
this.form = this.fb.group({
username: ['', [required, maxLength(255)]],
password: ['', [required, ...getPasswordValidators(this.injector)]],
email: ['', [required, email]],
});
}
onSubmit() {
if (this.form.invalid) return;
this.inProgress = true;
const newUser = {
userName: this.form.get('username').value,
password: this.form.get('password').value,
emailAddress: this.form.get('email').value,
appName: 'Angular',
} as RegisterDto;
this.accountService
.register(newUser)
.pipe(
switchMap(() =>
this.authService.login({
username: newUser.userName,
password: newUser.password,
redirectUrl: getRedirectUrl(this.injector),
}),
),
catchError(err => {
this.toasterService.error(
snq(() => err.error.error_description) ||
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
'Error',
{ life: 7000 },
);
return throwError(err);
}),
finalize(() => (this.inProgress = false)),
)
.subscribe();
}
}

@ -0,0 +1,49 @@
<h4>{{ 'AbpAccount::ResetPassword' | abpLocalization }}</h4>
<form
*ngIf="!isPasswordReset; else passwordResetTemplate"
[formGroup]="form"
[mapErrorsFn]="mapErrorsFn"
(ngSubmit)="onSubmit()"
validateOnSubmit
>
<p>{{ 'AbpAccount::ResetPassword_Information' | abpLocalization }}</p>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span>
<input type="password" id="input-password" class="form-control" formControlName="password" />
</div>
<div class="form-group">
<label for="input-confirm-password">{{ 'AbpAccount::ConfirmPassword' | abpLocalization }}</label
><span> * </span>
<input
type="password"
id="input-confirm-password"
class="form-control"
formControlName="confirmPassword"
/>
</div>
<button class="mr-2 btn btn-secondary" type="button" routerLink="/account/login">
{{ 'AbpAccount::Cancel' | abpLocalization }}
</button>
<abp-button
buttonType="submit"
buttonClass="mr-2 btn btn-primary"
[loading]="inProgress"
(click)="onSubmit()"
>
{{ 'AbpAccount::Submit' | abpLocalization }}
</abp-button>
</form>
<ng-template #passwordResetTemplate>
<p>
{{ 'AbpAccount::YourPasswordIsSuccessfullyReset' | abpLocalization }}
</p>
<a routerLink="/account/login">
<button class="d-block mt-2 mb-3 btn btn-primary">
{{ 'AbpAccount::BackToLogin' | abpLocalization }}
</button>
</a>
</ng-template>

@ -0,0 +1,70 @@
import { getPasswordValidators } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { comparePasswords, Validation } from '@ngx-validate/core';
import { finalize } from 'rxjs/operators';
import { AccountService } from '../../proxy/account/account.service';
const PASSWORD_FIELDS = ['password', 'confirmPassword'];
@Component({
selector: 'abp-reset-password',
templateUrl: './reset-password.component.html',
})
export class ResetPasswordComponent implements OnInit {
form: FormGroup;
inProgress = false;
isPasswordReset = false;
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
constructor(
private fb: FormBuilder,
private accountService: AccountService,
private route: ActivatedRoute,
private router: Router,
private injector: Injector,
) {}
ngOnInit(): void {
this.route.queryParams.subscribe(({ userId, resetToken }) => {
if (!userId || !resetToken) this.router.navigateByUrl('/account/login');
this.form = this.fb.group(
{
userId: [userId, [Validators.required]],
resetToken: [resetToken, [Validators.required]],
password: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
confirmPassword: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
},
{
validators: [comparePasswords(PASSWORD_FIELDS)],
},
);
});
}
onSubmit() {
if (this.form.invalid || this.inProgress) return;
this.inProgress = true;
this.accountService
.resetPassword({
userId: this.form.get('userId').value,
resetToken: this.form.get('resetToken').value,
password: this.form.get('password').value,
})
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => {
this.isPasswordReset = true;
});
}
}

@ -0,0 +1,63 @@
<ng-container *ngIf="(currentTenant$ | async) || {} as currentTenant">
<div class="card shadow-sm rounded mb-3">
<div class="card-body px-5">
<div class="row">
<div class="col">
<span style="font-size: 0.8em;" class="text-uppercase text-muted">{{
'AbpUiMultiTenancy::Tenant' | abpLocalization
}}</span
><br />
<h6 class="m-0 d-inline-block">
<i>{{ currentTenant.name || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}</i>
</h6>
</div>
<div class="col-auto">
<a
id="AbpTenantSwitchLink"
href="javascript:void(0);"
class="btn btn-sm mt-3 btn-outline-primary"
(click)="onSwitch()"
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a
>
</div>
</div>
</div>
</div>
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h5>Switch Tenant</h5>
</ng-template>
<ng-template #abpBody>
<form (ngSubmit)="save()">
<div class="mt-2">
<div class="form-group">
<label for="name">{{ 'AbpUiMultiTenancy::Name' | abpLocalization }}</label>
<input
[(ngModel)]="name"
type="text"
id="name"
name="tenant"
class="form-control"
autofocus
/>
</div>
<p>{{ 'AbpUiMultiTenancy::SwitchTenantHint' | abpLocalization }}</p>
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button #abpClose type="button" class="btn btn-secondary">
{{ 'AbpTenantManagement::Cancel' | abpLocalization }}
</button>
<abp-button
type="abp-button"
iconClass="fa fa-check"
(click)="save()"
[disabled]="currentTenant?.name === name"
>
<span>{{ 'AbpTenantManagement::Save' | abpLocalization }}</span>
</abp-button>
</ng-template>
</abp-modal>
</ng-container>

@ -0,0 +1,73 @@
import {
AbpApplicationConfigurationService,
AbpTenantService,
ConfigStateService,
CurrentTenantDto,
SessionStateService,
} from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { Account } from '../../models/account';
@Component({
selector: 'abp-tenant-box',
templateUrl: './tenant-box.component.html',
})
export class TenantBoxComponent
implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
currentTenant$ = this.sessionState.getTenant$();
name: string;
isModalVisible: boolean;
modalBusy: boolean;
constructor(
private toasterService: ToasterService,
private tenantService: AbpTenantService,
private sessionState: SessionStateService,
private configState: ConfigStateService,
private appConfigService: AbpApplicationConfigurationService,
) {}
onSwitch() {
const tenant = this.sessionState.getTenant();
this.name = tenant?.name;
this.isModalVisible = true;
}
save() {
if (!this.name) {
this.setTenant(null);
this.isModalVisible = false;
return;
}
this.modalBusy = true;
this.tenantService
.findTenantByName(this.name, {})
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(({ success, tenantId: id, ...tenant }) => {
if (!success) {
this.showError();
return;
}
this.setTenant({ ...tenant, id, isAvailable: true });
this.isModalVisible = false;
});
}
private setTenant(tenant: CurrentTenantDto) {
this.sessionState.setTenant(tenant);
this.appConfigService.get().subscribe(res => this.configState.setState(res));
}
private showError() {
this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
messageLocalizationParams: [this.name],
});
}
}

@ -0,0 +1,11 @@
export const enum eAccountComponents {
Login = 'Account.LoginComponent',
Register = 'Account.RegisterComponent',
ForgotPassword = 'Account.ForgotPasswordComponent',
ResetPassword = 'Account.ResetPasswordComponent',
ManageProfile = 'Account.ManageProfileComponent',
TenantBox = 'Account.TenantBoxComponent',
AuthWrapper = 'Account.AuthWrapperComponent',
ChangePassword = 'Account.ChangePasswordComponent',
PersonalSettings = 'Account.PersonalSettingsComponent',
}

@ -0,0 +1,15 @@
import { AuthService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthenticationFlowGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate() {
if (this.authService.isInternalAuth) return true;
this.authService.navigateToLogin();
return false;
}
}

@ -0,0 +1 @@
export * from './authentication-flow.guard';

@ -0,0 +1,12 @@
import { TemplateRef } from '@angular/core';
export namespace Account {
//tslint:disable
export interface TenantBoxComponentInputs {}
export interface TenantBoxComponentOutputs {}
export interface PersonalSettingsComponentInputs {}
export interface PersonalSettingsComponentOutputs {}
export interface ChangePasswordComponentInputs {}
export interface ChangePasswordComponentOutputs {}
// tslint:enable
}

@ -0,0 +1,3 @@
export interface AccountConfigOptions {
redirectUrl?: string;
}

@ -0,0 +1,2 @@
export * from './account';
export * from './config-options';

@ -0,0 +1,17 @@
# Proxy Generation Output
This directory includes the output of the latest proxy generation.
The files and folders in it will be overwritten when proxy generation is run again.
Therefore, please do not place your own content in this folder.
In addition, `generate-proxy.json` works like a lock file.
It includes information used by the proxy generator, so please do not delete or modify it.
Finally, the name of the files and folders should not be changed for two reasons:
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content.
- ABP Suite generates files which include imports from this folder.
> **Important Notice:** If you are building a module and are planning to publish to npm,
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case,
> please make sure you export files directly and not from barrel exports. In other words,
> do not include index.ts exports in your public-api.ts exports.

@ -0,0 +1,37 @@
import type { RegisterDto, ResetPasswordDto, SendPasswordResetCodeDto } from './models';
import { RestService } from '@abp/ng.core';
import { Injectable } from '@angular/core';
import type { IdentityUserDto } from '../identity/models';
@Injectable({
providedIn: 'root',
})
export class AccountService {
apiName = 'AbpAccount';
register = (input: RegisterDto) =>
this.restService.request<any, IdentityUserDto>({
method: 'POST',
url: '/api/account/register',
body: input,
},
{ apiName: this.apiName });
resetPassword = (input: ResetPasswordDto) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/account/reset-password',
body: input,
},
{ apiName: this.apiName });
sendPasswordResetCode = (input: SendPasswordResetCodeDto) =>
this.restService.request<any, void>({
method: 'POST',
url: '/api/account/send-password-reset-code',
body: input,
},
{ apiName: this.apiName });
constructor(private restService: RestService) {}
}

@ -0,0 +1,2 @@
export * from './account.service';
export * from './models';

@ -0,0 +1,20 @@
export interface RegisterDto {
userName: string;
emailAddress: string;
password: string;
appName: string;
}
export interface ResetPasswordDto {
userId?: string;
resetToken: string;
password: string;
}
export interface SendPasswordResetCodeDto {
email: string;
appName: string;
returnUrl?: string;
returnUrlHash?: string;
}

@ -0,0 +1,15 @@
import type { ExtensibleFullAuditedEntityDto } from '@abp/ng.core';
export interface IdentityUserDto extends ExtensibleFullAuditedEntityDto<string> {
tenantId?: string;
userName?: string;
name?: string;
surname?: string;
email?: string;
emailConfirmed: boolean;
phoneNumber?: string;
phoneNumberConfirmed: boolean;
lockoutEnabled: boolean;
lockoutEnd?: string;
concurrencyStamp?: string;
}

@ -0,0 +1,3 @@
import * as Account from './account';
import * as Identity from './identity';
export { Account, Identity };

@ -0,0 +1 @@
export * from './manage-profile.state.service';

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { InternalStore, Profile } from '@abp/ng.core';
import { Observable } from 'rxjs';
export interface ManageProfileState {
profile: Profile.Response;
}
@Injectable({ providedIn: 'root' })
export class ManageProfileStateService {
private readonly store = new InternalStore({} as ManageProfileState);
get createOnUpdateStream() {
return this.store.sliceUpdate;
}
getProfile$(): Observable<Profile.Response> {
return this.store.sliceState(state => state.profile);
}
getProfile(): Profile.Response {
return this.store.state.profile;
}
setProfile(profile: Profile.Response) {
this.store.patch({ profile });
}
}

@ -0,0 +1,6 @@
import { InjectionToken } from '@angular/core';
import { AccountConfigOptions } from '../models/config-options';
export const ACCOUNT_CONFIG_OPTIONS = new InjectionToken<AccountConfigOptions>(
'ACCOUNT_CONFIG_OPTIONS',
);

@ -0,0 +1 @@
export * from './config-options.token';

@ -0,0 +1,9 @@
import { Injector } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ACCOUNT_CONFIG_OPTIONS } from '../tokens';
export function getRedirectUrl(injector: Injector) {
const route = injector.get(ActivatedRoute);
const options = injector.get(ACCOUNT_CONFIG_OPTIONS);
return route.snapshot.queryParams.returnUrl || options.redirectUrl || '/';
}

@ -0,0 +1,8 @@
import { AccountConfigOptions } from '../models/config-options';
export function accountConfigOptionsFactory(options: AccountConfigOptions) {
return {
redirectUrl: '/',
...options,
};
}

@ -0,0 +1,2 @@
export * from './auth-utils';
export * from './factory-utils';

@ -0,0 +1,9 @@
export * from './lib/account.module';
export * from './lib/components';
export * from './lib/enums';
export * from './lib/guards';
export * from './lib/models';
export * from './lib/services';
export * from './lib/tokens';
export * from './lib/proxy/account';
export * from './lib/proxy/identity';

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.prod.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableResourceInlining": true
},
"exclude": ["src/test.ts", "**/*.spec.ts"]
}

@ -0,0 +1,6 @@
{
"extends": "./tsconfig.lib.json",
"angularCompilerOptions": {
"enableIvy": false
}
}

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.prod.json",
"compilerOptions": {
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["**/*.spec.ts", "**/*.d.ts"]
}

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"abp",
"camelCase"
],
"component-selector": [
true,
"element",
"abp",
"kebab-case"
]
}
}

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
@ -29,7 +29,7 @@ import { LocaleProvider } from './providers/locale.provider';
import { LocalizationService } from './services/localization.service';
import { ProfileState } from './states/profile.state';
import { oAuthStorage } from './strategies/auth-flow.strategy';
import { CORE_OPTIONS, coreOptionsFactory } from './tokens/options.token';
import { coreOptionsFactory, CORE_OPTIONS } from './tokens/options.token';
import { noop } from './utils/common-utils';
import './utils/date-extensions';
import { getInitialData, localeInitializer } from './utils/initial-utils';

@ -16,7 +16,7 @@ export class AuthGuard implements CanActivate {
return true;
}
this.authService.initLogin();
this.authService.navigateToLogin();
return false;
}
}

@ -4,9 +4,17 @@ export namespace Session {
export interface State {
language: string;
tenant: CurrentTenantDto;
/**
*
* @deprecated To be deleted in v5.0
*/
sessionDetail: SessionDetail;
}
/**
*
* @deprecated To be deleted in v5.0
*/
export interface SessionDetail {
openedTabCount: number;
lastExitTime: number;

@ -1,47 +1,61 @@
import { Injectable, Injector } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy';
import { from, Observable } from 'rxjs';
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import {
AuthFlowStrategy,
AUTH_FLOW_STRATEGY,
LoginParams,
} from '../strategies/auth-flow.strategy';
import { EnvironmentService } from './environment.service';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private flow: string;
private strategy: AuthFlowStrategy;
get isInternalAuth() {
return this.strategy.isInternalAuth;
}
constructor(private environment: EnvironmentService, private injector: Injector) {
this.setStrategy();
this.listenToSetEnvironment();
}
private setStrategy = () => {
const flow = this.environment.getEnvironment().oAuthConfig.responseType || 'password';
if (this.flow === flow) return;
if (this.strategy) this.strategy.destroy();
this.flow = flow;
this.strategy = AUTH_FLOW_STRATEGY.Code(this.injector);
};
private listenToSetEnvironment() {
this.environment.createOnUpdateStream(state => state.oAuthConfig).subscribe(this.setStrategy);
}
constructor(protected injector: Injector) {}
async init() {
return await this.strategy.init();
const environmentService = this.injector.get(EnvironmentService);
return environmentService
.getEnvironment$()
.pipe(
map(env => env?.oAuthConfig),
filter(oAuthConfig => !!oAuthConfig),
tap(oAuthConfig => {
this.strategy =
oAuthConfig.responseType === 'code'
? AUTH_FLOW_STRATEGY.Code(this.injector)
: AUTH_FLOW_STRATEGY.Password(this.injector);
}),
switchMap(() => from(this.strategy.init())),
take(1),
)
.toPromise();
}
logout(): Observable<any> {
return this.strategy.logout();
}
/**
* @deprecated Use navigateToLogin method instead. To be deleted in v5.0
*/
initLogin() {
this.strategy.login();
this.strategy.navigateToLogin();
}
navigateToLogin() {
this.strategy.navigateToLogin();
}
login(params: LoginParams) {
return this.strategy.login(params);
}
}

@ -6,12 +6,6 @@ import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenan
import { InternalStore } from '../utils/internal-store-utils';
import { ConfigStateService } from './config-state.service';
export interface SessionDetail {
openedTabCount: number;
lastExitTime: number;
remember: boolean;
}
@Injectable({
providedIn: 'root',
})

@ -1,11 +1,23 @@
import { HttpHeaders } from '@angular/common/http';
import { Injector } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { AuthConfig, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { Observable, of } from 'rxjs';
import { AuthConfig, OAuthInfoEvent, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { from, Observable, of } from 'rxjs';
import { filter, switchMap, tap } from 'rxjs/operators';
import { RestOccurError } from '../actions/rest.actions';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
import { SessionStateService } from '../services/session-state.service';
import { ConfigStateService } from '../services/config-state.service';
import { EnvironmentService } from '../services/environment.service';
export interface LoginParams {
username: string;
password: string;
rememberMe?: boolean;
redirectUrl?: string;
}
export const oAuthStorage = localStorage;
export abstract class AuthFlowStrategy {
@ -17,9 +29,9 @@ export abstract class AuthFlowStrategy {
protected oAuthService: OAuthService;
protected oAuthConfig: AuthConfig;
abstract checkIfInternalAuth(): boolean;
abstract login(): void;
abstract navigateToLogin(): void;
abstract logout(): Observable<any>;
abstract destroy(): void;
abstract login(params?: LoginParams): Observable<any>;
private catchError = err => this.store.dispatch(new RestOccurError(err));
@ -39,7 +51,16 @@ export abstract class AuthFlowStrategy {
if (shouldClear) clearOAuthStorage(oAuthStorage);
this.oAuthService.configure(this.oAuthConfig);
return this.oAuthService.loadDiscoveryDocument().catch(this.catchError);
return this.oAuthService
.loadDiscoveryDocument()
.then(() => {
if (this.oAuthService.hasValidAccessToken() || !this.oAuthService.getRefreshToken()) {
return Promise.resolve();
}
return this.oAuthService.refreshToken() as Promise<any>;
})
.catch(this.catchError);
}
}
@ -50,17 +71,10 @@ export class AuthCodeFlowStrategy extends AuthFlowStrategy {
return super
.init()
.then(() => this.oAuthService.tryLogin())
.then(() => {
if (this.oAuthService.hasValidAccessToken() || !this.oAuthService.getRefreshToken()) {
return Promise.resolve();
}
return this.oAuthService.refreshToken() as Promise<any>;
})
.then(() => this.oAuthService.setupAutomaticSilentRefresh({}, 'access_token'));
}
login() {
navigateToLogin() {
this.oAuthService.initCodeFlow();
}
@ -70,18 +84,115 @@ export class AuthCodeFlowStrategy extends AuthFlowStrategy {
}
logout() {
this.oAuthService.revokeTokenAndLogout();
// TODO: no need to return of(null). It may be removed in v5.0.
return from(this.oAuthService.revokeTokenAndLogout());
}
login() {
this.oAuthService.initCodeFlow();
return of(null);
}
}
destroy() {}
export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
readonly isInternalAuth = true;
private cookieKey = 'rememberMe';
private storageKey = 'passwordFlow';
private appConfigService = this.injector.get(AbpApplicationConfigurationService);
private listenToTokenExpiration() {
this.oAuthService.events
.pipe(
filter(
event =>
event instanceof OAuthInfoEvent &&
event.type === 'token_expires' &&
event.info === 'access_token',
),
)
.subscribe(() => {
if (this.oAuthService.getRefreshToken()) {
this.oAuthService.refreshToken();
} else {
this.oAuthService.logOut();
this.appConfigService.get().subscribe(res => {
this.configState.setState(res);
});
}
});
}
private setRememberMe(remember: boolean) {
this.removeRememberMe();
localStorage.setItem(this.storageKey, 'true');
document.cookie = `${this.cookieKey}=true${
remember ? ';expires=Fri, 31 Dec 9999 23:59:59 GMT' : ''
}`;
}
private removeRememberMe() {
localStorage.removeItem(this.storageKey);
document.cookie = this.cookieKey + '= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
}
async init() {
if (!getCookieValueByName('rememberMe') && localStorage.getItem(this.storageKey)) {
this.oAuthService.logOut();
}
return super.init().then(() => this.listenToTokenExpiration());
}
navigateToLogin() {
const router = this.injector.get(Router);
router.navigateByUrl('/account/login');
}
checkIfInternalAuth() {
return true;
}
login(params: LoginParams): Observable<any> {
const sessionState = this.injector.get(SessionStateService);
const router = this.injector.get(Router);
const tenant = sessionState.getTenant();
return from(
this.oAuthService.fetchTokenUsingPasswordFlow(
params.username,
params.password,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
),
).pipe(
switchMap(() => this.appConfigService.get()),
tap(res => {
this.configState.setState(res);
this.setRememberMe(params.rememberMe);
router.navigate([params.redirectUrl || '/']);
}),
);
}
logout() {
const router = this.injector.get(Router);
return from(this.oAuthService.revokeTokenAndLogout()).pipe(
switchMap(() => this.appConfigService.get()),
tap(res => {
this.configState.setState(res);
router.navigateByUrl('/');
this.removeRememberMe();
}),
);
}
}
export const AUTH_FLOW_STRATEGY = {
Code(injector: Injector) {
return new AuthCodeFlowStrategy(injector);
},
Password(injector: Injector) {
return new AuthPasswordFlowStrategy(injector);
},
};
export function clearOAuthStorage(storage: OAuthStorage = oAuthStorage) {
@ -114,3 +225,8 @@ function shouldStorageClear(clientId: string, storage: OAuthStorage): boolean {
if (shouldClear) storage.setItem(key, clientId);
return shouldClear;
}
function getCookieValueByName(name: string) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : '';
}

@ -1,4 +1,3 @@
export * from './auth-flow.strategy';
export * from './container.strategy';
export * from './content-security.strategy';
export * from './content.strategy';

@ -21,12 +21,12 @@ describe('AuthGuard', () => {
expect(guard.canActivate()).toBe(true);
});
it('should execute the initLogin method of the authService', () => {
it('should execute the navigateToLogin method of the authService', () => {
const authService = spectator.inject(AuthService);
spectator.inject(OAuthService).hasValidAccessToken.andReturn(false);
const initLoginSpy = jest.spyOn(authService, 'initLogin');
const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin');
expect(guard.canActivate()).toBe(false);
expect(initLoginSpy).toHaveBeenCalled();
expect(navigateToLoginSpy).toHaveBeenCalled();
});
});

@ -1,9 +1,7 @@
import { registerLocaleData } from '@angular/common';
import { Injector } from '@angular/core';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { tap } from 'rxjs/operators';
import { ApplicationConfiguration } from '../models/application-configuration';
import { ABP } from '../models/common';
import { Environment } from '../models/environment';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';

@ -43,7 +43,12 @@
}}</label>
</div>
<div [@collapse]="form.get('smtpUseDefaultCredentials').value ? 'collapsed' : 'expanded'">
<div
[@collapse]="{
value: form.get('smtpUseDefaultCredentials').value ? 'collapsed' : 'expanded',
params: { time: '200ms', easing: 'linear' }
}"
>
<div class="form-group">
<label>{{ 'AbpSettingManagement::SmtpDomain' | abpLocalization }}</label>
<input type="text" class="form-control" formControlName="smtpDomain" />

@ -17,7 +17,7 @@ export function configureSettingTabs(settingTabs: SettingTabsService) {
settingTabs.add([
{
name: eSettingManamagementSettingTabNames.EmailSettingGroup,
order: 1,
order: 100,
requiredPolicy: 'SettingManagement.Emailing',
component: EmailSettingGroupComponent,
},

@ -1,6 +1,5 @@
import { AuthService, ConfigStateService, CurrentUserDto, EnvironmentService } from '@abp/ng.core';
import { AuthService, ConfigStateService, CurrentUserDto } from '@abp/ng.core';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
@Component({
@ -8,7 +7,7 @@ import { Observable } from 'rxjs';
// tslint:disable-next-line: component-max-inline-declarations
template: `
<ng-template #loginBtn>
<a role="button" class="nav-link pointer" (click)="initLogin()">{{
<a role="button" class="nav-link pointer" (click)="navigateToLogin()">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</ng-template>
@ -36,7 +35,7 @@ import { Observable } from 'rxjs';
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" [href]="manageProfileUrl"
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
@ -53,24 +52,13 @@ export class CurrentUserComponent {
return window.innerWidth < 992;
}
get manageProfileUrl() {
return `${this.environment.getEnvironment().oAuthConfig.issuer}/Account/Manage?returnUrl=${
window.location.href
}`;
}
constructor(
private authService: AuthService,
private router: Router,
private configState: ConfigStateService,
private environment: EnvironmentService,
) {}
constructor(private authService: AuthService, private configState: ConfigStateService) {}
initLogin() {
this.authService.initLogin();
navigateToLogin() {
this.authService.navigateToLogin();
}
logout() {
logout() {
this.authService.logout().subscribe();
}
}

@ -275,8 +275,8 @@ export class ErrorHandler {
});
}
protected navigateToLogin() {
this.injector.get(AuthService).initLogin();
private navigateToLogin() {
this.injector.get(AuthService).navigateToLogin();
}
createErrorComponent(instance: Partial<HttpErrorWrapperComponent>) {

@ -22,6 +22,8 @@
"@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"],
"@abp/ng.components/tree": ["packages/components/tree/src/public-api.ts"],
"@abp/ng.theme.basic": ["packages/theme-basic/src/public-api.ts"],
"@abp/ng.account": ["packages/account/src/public-api.ts"],
"@abp/ng.account/config": ["packages/account/config/src/public-api.ts"],
"@abp/ng.identity": ["packages/identity/src/public-api.ts"],
"@abp/ng.identity/config": ["packages/identity/config/src/public-api.ts"],
"@abp/ng.tenant-management": ["packages/tenant-management/src/public-api.ts"],

File diff suppressed because it is too large Load Diff

@ -14,6 +14,6 @@ export class HomeComponent {
constructor(private oAuthService: OAuthService, private authService: AuthService) {}
login() {
this.authService.initLogin();
this.authService.navigateToLogin();
}
}

@ -19,6 +19,9 @@
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.Application.Contracts\Volo.Abp.TenantManagement.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Application.Contracts\Volo.Abp.FeatureManagement.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Application.Contracts\Volo.Abp.SettingManagement.Application.Contracts.csproj" />
<!--<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>-->
<ProjectReference Include="..\..\..\..\..\modules\cms-kit\src\Volo.CmsKit.Application.Contracts\Volo.CmsKit.Application.Contracts.csproj" />
<!--</TEMPLATE-REMOVE>-->
</ItemGroup>
</Project>

@ -6,6 +6,9 @@ using Volo.Abp.ObjectExtending;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.TenantManagement;
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
using Volo.CmsKit.Admin;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName
{
@ -17,6 +20,9 @@ namespace MyCompanyName.MyProjectName
typeof(AbpPermissionManagementApplicationContractsModule),
typeof(AbpSettingManagementApplicationContractsModule),
typeof(AbpTenantManagementApplicationContractsModule),
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
typeof(CmsKitAdminApplicationContractsModule),
//</TEMPLATE-REMOVE>
typeof(AbpObjectExtendingModule)
)]
public class MyProjectNameApplicationContractsModule : AbpModule

@ -19,6 +19,9 @@
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.Application\Volo.Abp.TenantManagement.Application.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Application\Volo.Abp.FeatureManagement.Application.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Application\Volo.Abp.SettingManagement.Application.csproj" />
<!--<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>-->
<ProjectReference Include="..\..\..\..\..\modules\cms-kit\src\Volo.CmsKit.Application\Volo.CmsKit.Application.csproj" />
<!--</TEMPLATE-REMOVE>-->
</ItemGroup>
</Project>

@ -6,6 +6,9 @@ using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.TenantManagement;
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
using Volo.CmsKit;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName
{
@ -17,6 +20,9 @@ namespace MyCompanyName.MyProjectName
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
typeof(CmsKitApplicationModule),
//</TEMPLATE-REMOVE>
typeof(AbpSettingManagementApplicationModule)
)]
public class MyProjectNameApplicationModule : AbpModule

@ -17,6 +17,9 @@
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Domain.Shared\Volo.Abp.FeatureManagement.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.Domain.Shared\Volo.Abp.PermissionManagement.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Domain.Shared\Volo.Abp.SettingManagement.Domain.Shared.csproj" />
<!--<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>-->
<ProjectReference Include="..\..\..\..\..\modules\cms-kit\src\Volo.CmsKit.Domain.Shared\Volo.CmsKit.Domain.Shared.csproj" />
<!--</TEMPLATE-REMOVE>-->
</ItemGroup>
<ItemGroup>

@ -12,6 +12,9 @@ using Volo.Abp.SettingManagement;
using Volo.Abp.TenantManagement;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
using Volo.CmsKit;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName
{
@ -23,6 +26,9 @@ namespace MyCompanyName.MyProjectName
typeof(AbpIdentityServerDomainSharedModule),
typeof(AbpPermissionManagementDomainSharedModule),
typeof(AbpSettingManagementDomainSharedModule),
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
typeof(CmsKitDomainSharedModule),
//</TEMPLATE-REMOVE>
typeof(AbpTenantManagementDomainSharedModule)
)]
public class MyProjectNameDomainSharedModule : AbpModule

@ -1,4 +1,5 @@
using Volo.Abp.Threading;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Threading;
namespace MyCompanyName.MyProjectName
{
@ -10,6 +11,13 @@ namespace MyCompanyName.MyProjectName
{
OneTimeRunner.Run(() =>
{
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
{
cmsKit.EnableAll();
});
//</TEMPLATE-REMOVE>
/* You can configure (enable/disable) global features of the used modules here.
*
* YOU CAN SAFELY DELETE THIS CLASS AND REMOVE ITS USAGES IF YOU DON'T NEED TO IT!

@ -22,6 +22,9 @@
<ProjectReference Include="..\..\..\..\..\modules\tenant-management\src\Volo.Abp.TenantManagement.Domain\Volo.Abp.TenantManagement.Domain.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\feature-management\src\Volo.Abp.FeatureManagement.Domain\Volo.Abp.FeatureManagement.Domain.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\setting-management\src\Volo.Abp.SettingManagement.Domain\Volo.Abp.SettingManagement.Domain.csproj" />
<!--<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>-->
<ProjectReference Include="..\..\..\..\..\modules\cms-kit\src\Volo.CmsKit.Domain\Volo.CmsKit.Domain.csproj" />
<!--</TEMPLATE-REMOVE>-->
</ItemGroup>
</Project>

@ -13,6 +13,9 @@ using Volo.Abp.PermissionManagement.Identity;
using Volo.Abp.PermissionManagement.IdentityServer;
using Volo.Abp.SettingManagement;
using Volo.Abp.TenantManagement;
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
using Volo.CmsKit;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName
{
@ -27,6 +30,9 @@ namespace MyCompanyName.MyProjectName
typeof(AbpPermissionManagementDomainIdentityServerModule),
typeof(AbpSettingManagementDomainModule),
typeof(AbpTenantManagementDomainModule),
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
typeof(CmsKitDomainModule),
//</TEMPLATE-REMOVE>
typeof(AbpEmailingModule)
)]
public class MyProjectNameDomainModule : AbpModule

@ -9,6 +9,9 @@ using Volo.Abp.IdentityServer.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.TenantManagement.EntityFrameworkCore;
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
using Volo.CmsKit.EntityFrameworkCore;
//</TEMPLATE-REMOVE>
namespace MyCompanyName.MyProjectName.EntityFrameworkCore
{
@ -39,6 +42,9 @@ namespace MyCompanyName.MyProjectName.EntityFrameworkCore
builder.ConfigureIdentityServer();
builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement();
//<TEMPLATE-REMOVE IF-NOT='CMS-KIT'>
builder.ConfigureCmsKit();
//</TEMPLATE-REMOVE>
/* Configure your own tables/entities inside the ConfigureMyProjectName method */

@ -0,0 +1,345 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace MyCompanyName.MyProjectName.Migrations
{
public partial class CmsKit : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CmsBlogFeatures",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BlogId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FeatureName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
IsEnabled = table.Column<bool>(type: "bit", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsBlogFeatures", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsBlogs",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Slug = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsBlogs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsComments",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
EntityId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Text = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
RepliedCommentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsComments", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsEntityTags",
columns: table => new
{
TagId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
EntityId = table.Column<string>(type: "nvarchar(450)", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsEntityTags", x => new { x.EntityId, x.TagId });
});
migrationBuilder.CreateTable(
name: "CmsMediaDescriptors",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
MimeType = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Size = table.Column<long>(type: "bigint", maxLength: 2147483647, nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsMediaDescriptors", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsPages",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Title = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Slug = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 2147483647, nullable: true),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsPages", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsRatings",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
EntityId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
StarCount = table.Column<short>(type: "smallint", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsRatings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsTags",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsTags", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsUserReactions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
EntityType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
EntityId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
ReactionName = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsUserReactions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsUsers",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
Surname = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
PhoneNumber = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CmsBlogPosts",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BlogId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Title = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Slug = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
ShortDescription = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Content = table.Column<string>(type: "nvarchar(max)", maxLength: 2147483647, nullable: true),
CoverImageMediaId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
AuthorId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CmsBlogPosts", x => x.Id);
table.ForeignKey(
name: "FK_CmsBlogPosts_CmsUsers_AuthorId",
column: x => x.AuthorId,
principalTable: "CmsUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_AuthorId",
table: "CmsBlogPosts",
column: "AuthorId");
migrationBuilder.CreateIndex(
name: "IX_CmsBlogPosts_Slug_BlogId",
table: "CmsBlogPosts",
columns: new[] { "Slug", "BlogId" });
migrationBuilder.CreateIndex(
name: "IX_CmsComments_TenantId_EntityType_EntityId",
table: "CmsComments",
columns: new[] { "TenantId", "EntityType", "EntityId" });
migrationBuilder.CreateIndex(
name: "IX_CmsComments_TenantId_RepliedCommentId",
table: "CmsComments",
columns: new[] { "TenantId", "RepliedCommentId" });
migrationBuilder.CreateIndex(
name: "IX_CmsEntityTags_TenantId_EntityId_TagId",
table: "CmsEntityTags",
columns: new[] { "TenantId", "EntityId", "TagId" });
migrationBuilder.CreateIndex(
name: "IX_CmsPages_TenantId_Slug",
table: "CmsPages",
columns: new[] { "TenantId", "Slug" });
migrationBuilder.CreateIndex(
name: "IX_CmsRatings_TenantId_EntityType_EntityId_CreatorId",
table: "CmsRatings",
columns: new[] { "TenantId", "EntityType", "EntityId", "CreatorId" });
migrationBuilder.CreateIndex(
name: "IX_CmsTags_TenantId_Name",
table: "CmsTags",
columns: new[] { "TenantId", "Name" });
migrationBuilder.CreateIndex(
name: "IX_CmsUserReactions_TenantId_CreatorId_EntityType_EntityId_ReactionName",
table: "CmsUserReactions",
columns: new[] { "TenantId", "CreatorId", "EntityType", "EntityId", "ReactionName" });
migrationBuilder.CreateIndex(
name: "IX_CmsUserReactions_TenantId_EntityType_EntityId_ReactionName",
table: "CmsUserReactions",
columns: new[] { "TenantId", "EntityType", "EntityId", "ReactionName" });
migrationBuilder.CreateIndex(
name: "IX_CmsUsers_TenantId_Email",
table: "CmsUsers",
columns: new[] { "TenantId", "Email" });
migrationBuilder.CreateIndex(
name: "IX_CmsUsers_TenantId_UserName",
table: "CmsUsers",
columns: new[] { "TenantId", "UserName" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CmsBlogFeatures");
migrationBuilder.DropTable(
name: "CmsBlogPosts");
migrationBuilder.DropTable(
name: "CmsBlogs");
migrationBuilder.DropTable(
name: "CmsComments");
migrationBuilder.DropTable(
name: "CmsEntityTags");
migrationBuilder.DropTable(
name: "CmsMediaDescriptors");
migrationBuilder.DropTable(
name: "CmsPages");
migrationBuilder.DropTable(
name: "CmsRatings");
migrationBuilder.DropTable(
name: "CmsTags");
migrationBuilder.DropTable(
name: "CmsUserReactions");
migrationBuilder.DropTable(
name: "CmsUsers");
}
}
}

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

Loading…
Cancel
Save