Browse Source

Merge pull request #7861 from abpframework/feat/volo-5292

Angular UI: Resource owner password flow
pull/7990/head
Levent Arman Özak 6 years ago
committed by GitHub
parent
commit
85bc35a78d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      npm/ng-packs/angular.json
  2. 4
      npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts
  3. 2
      npm/ng-packs/apps/dev-app/src/app/app.module.ts
  4. 2
      npm/ng-packs/apps/dev-app/src/app/home/home.component.ts
  5. 5
      npm/ng-packs/apps/dev-app/src/environments/environment.ts
  6. 3
      npm/ng-packs/packages/account/README.md
  7. 7
      npm/ng-packs/packages/account/config/ng-package.json
  8. 12
      npm/ng-packs/packages/account/config/src/account-config.module.ts
  9. 1
      npm/ng-packs/packages/account/config/src/enums/index.ts
  10. 6
      npm/ng-packs/packages/account/config/src/enums/route-names.ts
  11. 1
      npm/ng-packs/packages/account/config/src/providers/index.ts
  12. 39
      npm/ng-packs/packages/account/config/src/providers/route.provider.ts
  13. 3
      npm/ng-packs/packages/account/config/src/public-api.ts
  14. 6
      npm/ng-packs/packages/account/jest.config.js
  15. 8
      npm/ng-packs/packages/account/ng-package.json
  16. 6
      npm/ng-packs/packages/account/ngcc.config.js
  17. 16
      npm/ng-packs/packages/account/package.json
  18. 93
      npm/ng-packs/packages/account/src/lib/account-routing.module.ts
  19. 63
      npm/ng-packs/packages/account/src/lib/account.module.ts
  20. 25
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
  21. 28
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
  22. 48
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html
  23. 99
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  24. 40
      npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.html
  25. 35
      npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts
  26. 8
      npm/ng-packs/packages/account/src/lib/components/index.ts
  27. 60
      npm/ng-packs/packages/account/src/lib/components/login/login.component.html
  28. 80
      npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
  29. 62
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html
  30. 45
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
  31. 46
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html
  32. 58
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  33. 51
      npm/ng-packs/packages/account/src/lib/components/register/register.component.html
  34. 102
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  35. 49
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.html
  36. 70
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
  37. 63
      npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.html
  38. 73
      npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts
  39. 11
      npm/ng-packs/packages/account/src/lib/enums/components.ts
  40. 1
      npm/ng-packs/packages/account/src/lib/enums/index.ts
  41. 15
      npm/ng-packs/packages/account/src/lib/guards/authentication-flow.guard.ts
  42. 1
      npm/ng-packs/packages/account/src/lib/guards/index.ts
  43. 12
      npm/ng-packs/packages/account/src/lib/models/account.ts
  44. 3
      npm/ng-packs/packages/account/src/lib/models/config-options.ts
  45. 2
      npm/ng-packs/packages/account/src/lib/models/index.ts
  46. 17
      npm/ng-packs/packages/account/src/lib/proxy/README.md
  47. 37
      npm/ng-packs/packages/account/src/lib/proxy/account/account.service.ts
  48. 2
      npm/ng-packs/packages/account/src/lib/proxy/account/index.ts
  49. 20
      npm/ng-packs/packages/account/src/lib/proxy/account/models.ts
  50. 4745
      npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json
  51. 1
      npm/ng-packs/packages/account/src/lib/proxy/identity/index.ts
  52. 15
      npm/ng-packs/packages/account/src/lib/proxy/identity/models.ts
  53. 3
      npm/ng-packs/packages/account/src/lib/proxy/index.ts
  54. 1
      npm/ng-packs/packages/account/src/lib/services/index.ts
  55. 28
      npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts
  56. 6
      npm/ng-packs/packages/account/src/lib/tokens/config-options.token.ts
  57. 1
      npm/ng-packs/packages/account/src/lib/tokens/index.ts
  58. 9
      npm/ng-packs/packages/account/src/lib/utils/auth-utils.ts
  59. 8
      npm/ng-packs/packages/account/src/lib/utils/factory-utils.ts
  60. 2
      npm/ng-packs/packages/account/src/lib/utils/index.ts
  61. 9
      npm/ng-packs/packages/account/src/public-api.ts
  62. 19
      npm/ng-packs/packages/account/tsconfig.lib.json
  63. 6
      npm/ng-packs/packages/account/tsconfig.lib.prod.json
  64. 11
      npm/ng-packs/packages/account/tsconfig.spec.json
  65. 17
      npm/ng-packs/packages/account/tslint.json
  66. 4
      npm/ng-packs/packages/core/src/lib/core.module.ts
  67. 2
      npm/ng-packs/packages/core/src/lib/guards/auth.guard.ts
  68. 8
      npm/ng-packs/packages/core/src/lib/models/session.ts
  69. 60
      npm/ng-packs/packages/core/src/lib/services/auth.service.ts
  70. 6
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  71. 148
      npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts
  72. 1
      npm/ng-packs/packages/core/src/lib/strategies/index.ts
  73. 6
      npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts
  74. 2
      npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts
  75. 7
      npm/ng-packs/packages/setting-management/config/src/components/email-setting-group/email-setting-group.component.html
  76. 2
      npm/ng-packs/packages/setting-management/config/src/providers/setting-tab.provider.ts
  77. 26
      npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts
  78. 4
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  79. 2
      npm/ng-packs/tsconfig.json
  80. 2366
      npm/ng-packs/yarn.lock
  81. 2
      templates/app/angular/src/app/home/home.component.ts
  82. 4
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs
  83. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json
  84. 3
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs
  85. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json

37
npm/ng-packs/angular.json

@ -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",

4
npm/ng-packs/apps/dev-app/src/app/app-routing.module.ts

@ -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()),

2
npm/ng-packs/apps/dev-app/src/app/app.module.ts

@ -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(),

2
npm/ng-packs/apps/dev-app/src/app/home/home.component.ts

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

5
npm/ng-packs/apps/dev-app/src/environments/environment.ts

@ -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: {

3
npm/ng-packs/packages/account/README.md

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

7
npm/ng-packs/packages/account/config/ng-package.json

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

12
npm/ng-packs/packages/account/config/src/account-config.module.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],
};
}
}

1
npm/ng-packs/packages/account/config/src/enums/index.ts

@ -0,0 +1 @@
export * from './route-names';

6
npm/ng-packs/packages/account/config/src/enums/route-names.ts

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

1
npm/ng-packs/packages/account/config/src/providers/index.ts

@ -0,0 +1 @@
export * from './route.provider';

39
npm/ng-packs/packages/account/config/src/providers/route.provider.ts

@ -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,
},
]);
};
}

3
npm/ng-packs/packages/account/config/src/public-api.ts

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

6
npm/ng-packs/packages/account/jest.config.js

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

8
npm/ng-packs/packages/account/ng-package.json

@ -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"]
}

6
npm/ng-packs/packages/account/ngcc.config.js

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

16
npm/ng-packs/packages/account/package.json

@ -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"
}
}

93
npm/ng-packs/packages/account/src/lib/account-routing.module.ts

@ -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 {}

63
npm/ng-packs/packages/account/src/lib/account.module.ts

@ -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));
}
}

25
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html

@ -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>

28
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts

@ -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,
) {}
}

48
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html

@ -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>

99
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts

@ -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'),
);
},
});
}
}

40
npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.html

@ -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>

35
npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts

@ -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;
});
}
}

8
npm/ng-packs/packages/account/src/lib/components/index.ts

@ -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';

60
npm/ng-packs/packages/account/src/lib/components/login/login.component.html

@ -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>

80
npm/ng-packs/packages/account/src/lib/components/login/login.component.ts

@ -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();
}
}

62
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html

@ -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>

45
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts

@ -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;
}
});
}
}

46
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html

@ -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>

58
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts

@ -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 });
});
}
}

51
npm/ng-packs/packages/account/src/lib/components/register/register.component.html

@ -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>

102
npm/ng-packs/packages/account/src/lib/components/register/register.component.ts

@ -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();
}
}

49
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.html

@ -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>

70
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts

@ -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;
});
}
}

63
npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.html

@ -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>

73
npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts

@ -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],
});
}
}

11
npm/ng-packs/packages/account/src/lib/enums/components.ts

@ -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',
}

1
npm/ng-packs/packages/account/src/lib/enums/index.ts

@ -0,0 +1 @@
export * from './components';

15
npm/ng-packs/packages/account/src/lib/guards/authentication-flow.guard.ts

@ -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;
}
}

1
npm/ng-packs/packages/account/src/lib/guards/index.ts

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

12
npm/ng-packs/packages/account/src/lib/models/account.ts

@ -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
}

3
npm/ng-packs/packages/account/src/lib/models/config-options.ts

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

2
npm/ng-packs/packages/account/src/lib/models/index.ts

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

17
npm/ng-packs/packages/account/src/lib/proxy/README.md

@ -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.

37
npm/ng-packs/packages/account/src/lib/proxy/account/account.service.ts

@ -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) {}
}

2
npm/ng-packs/packages/account/src/lib/proxy/account/index.ts

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

20
npm/ng-packs/packages/account/src/lib/proxy/account/models.ts

@ -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;
}

4745
npm/ng-packs/packages/account/src/lib/proxy/generate-proxy.json

File diff suppressed because it is too large

1
npm/ng-packs/packages/account/src/lib/proxy/identity/index.ts

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

15
npm/ng-packs/packages/account/src/lib/proxy/identity/models.ts

@ -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;
}

3
npm/ng-packs/packages/account/src/lib/proxy/index.ts

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

1
npm/ng-packs/packages/account/src/lib/services/index.ts

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

28
npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts

@ -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 });
}
}

6
npm/ng-packs/packages/account/src/lib/tokens/config-options.token.ts

@ -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',
);

1
npm/ng-packs/packages/account/src/lib/tokens/index.ts

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

9
npm/ng-packs/packages/account/src/lib/utils/auth-utils.ts

@ -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 || '/';
}

8
npm/ng-packs/packages/account/src/lib/utils/factory-utils.ts

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

2
npm/ng-packs/packages/account/src/lib/utils/index.ts

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

9
npm/ng-packs/packages/account/src/public-api.ts

@ -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';

19
npm/ng-packs/packages/account/tsconfig.lib.json

@ -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"]
}

6
npm/ng-packs/packages/account/tsconfig.lib.prod.json

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

11
npm/ng-packs/packages/account/tsconfig.spec.json

@ -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"]
}

17
npm/ng-packs/packages/account/tslint.json

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

4
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -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';

2
npm/ng-packs/packages/core/src/lib/guards/auth.guard.ts

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

8
npm/ng-packs/packages/core/src/lib/models/session.ts

@ -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;

60
npm/ng-packs/packages/core/src/lib/services/auth.service.ts

@ -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
npm/ng-packs/packages/core/src/lib/services/session-state.service.ts

@ -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',
})

148
npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts

@ -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
npm/ng-packs/packages/core/src/lib/strategies/index.ts

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

6
npm/ng-packs/packages/core/src/lib/tests/auth.guard.spec.ts

@ -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();
});
});

2
npm/ng-packs/packages/core/src/lib/utils/initial-utils.ts

@ -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';

7
npm/ng-packs/packages/setting-management/config/src/components/email-setting-group/email-setting-group.component.html

@ -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" />

2
npm/ng-packs/packages/setting-management/config/src/providers/setting-tab.provider.ts

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

26
npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts

@ -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();
}
}

4
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -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>) {

2
npm/ng-packs/tsconfig.json

@ -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"],

2366
npm/ng-packs/yarn.lock

File diff suppressed because it is too large

2
templates/app/angular/src/app/home/home.component.ts

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

4
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs

@ -13,6 +13,7 @@ using MyCompanyName.MyProjectName.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic;
using Microsoft.OpenApi.Models;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Web;
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.MultiTenancy;
@ -78,6 +79,9 @@ namespace MyCompanyName.MyProjectName
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
options.Applications["Angular"].RootUrl = configuration["App:ClientUrl"];
options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
});
}

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json

@ -1,6 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44305",
"ClientUrl": "http://localhost:4200",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},

3
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs

@ -114,6 +114,9 @@ namespace MyCompanyName.MyProjectName
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
options.Applications["Angular"].RootUrl = configuration["App:ClientUrl"];
options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
});
Configure<AbpBackgroundJobOptions>(options =>

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json

@ -1,6 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44301",
"ClientUrl": "http://localhost:4200",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},

Loading…
Cancel
Save