Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into feature/toast-component

pull/2606/head
mehmet-erim 6 years ago
parent
commit
12a66091cc
  1. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs
  2. 16
      framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs
  3. 2
      modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs
  4. 2
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  5. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs
  6. 2
      npm/ng-packs/apps/dev-app/src/index.html
  7. 2
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
  8. 33
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
  9. 4
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  10. 2
      npm/ng-packs/packages/account/src/lib/components/login/login.component.html
  11. 53
      npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
  12. 8
      npm/ng-packs/packages/account/src/lib/components/register/register.component.html
  13. 49
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  14. 8
      npm/ng-packs/packages/core/src/lib/actions/session.actions.ts
  15. 9
      npm/ng-packs/packages/core/src/lib/core.module.ts
  16. 7
      npm/ng-packs/packages/core/src/lib/models/session.ts
  17. 51
      npm/ng-packs/packages/core/src/lib/services/auth.service.ts
  18. 1
      npm/ng-packs/packages/core/src/lib/services/index.ts
  19. 20
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  20. 106
      npm/ng-packs/packages/core/src/lib/states/session.state.ts
  21. 2
      npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts
  22. 6
      npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts
  23. 10
      npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts
  24. 2
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  25. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts
  26. 56
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts
  27. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHandler.cs → framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationHelper.cs

@ -2,7 +2,7 @@
namespace Volo.Abp.Validation namespace Volo.Abp.Validation
{ {
public class ValidationHandler public class ValidationHelper
{ {
private const string EmailRegEx = @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"; private const string EmailRegEx = @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?";

16
framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs

@ -178,16 +178,16 @@ namespace Volo.Abp.Validation
public void Should_Validate_Emails() public void Should_Validate_Emails()
{ {
//Valid //Valid
ValidationHandler.IsValidEmailAddress("john.doe@domain.com").ShouldBe(true); ValidationHelper.IsValidEmailAddress("john.doe@domain.com").ShouldBe(true);
ValidationHandler.IsValidEmailAddress("ip@1.2.3.123").ShouldBe(true); ValidationHelper.IsValidEmailAddress("ip@1.2.3.123").ShouldBe(true);
ValidationHandler.IsValidEmailAddress("pharaoh@egyptian.museum").ShouldBe(true); ValidationHelper.IsValidEmailAddress("pharaoh@egyptian.museum").ShouldBe(true);
ValidationHandler.IsValidEmailAddress("john.doe+regexbuddy@gmail.com").ShouldBe(true); ValidationHelper.IsValidEmailAddress("john.doe+regexbuddy@gmail.com").ShouldBe(true);
ValidationHandler.IsValidEmailAddress("Mike.O'Dell@ireland.com").ShouldBe(true); ValidationHelper.IsValidEmailAddress("Mike.O'Dell@ireland.com").ShouldBe(true);
//Invalid //Invalid
ValidationHandler.IsValidEmailAddress("1024x768@60Hz").ShouldBe(false); ValidationHelper.IsValidEmailAddress("1024x768@60Hz").ShouldBe(false);
ValidationHandler.IsValidEmailAddress("not.a.valid.email").ShouldBe(false); ValidationHelper.IsValidEmailAddress("not.a.valid.email").ShouldBe(false);
ValidationHandler.IsValidEmailAddress("john@aol...com").ShouldBe(false); ValidationHelper.IsValidEmailAddress("john@aol...com").ShouldBe(false);
} }
[DependsOn(typeof(AbpAutofacModule))] [DependsOn(typeof(AbpAutofacModule))]

2
modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs

@ -64,7 +64,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(UserLoginInfo login) protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(UserLoginInfo login)
{ {
if (!ValidationHandler.IsValidEmailAddress(login.UserNameOrEmailAddress)) if (!ValidationHelper.IsValidEmailAddress(login.UserNameOrEmailAddress))
{ {
return; return;
} }

2
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -217,7 +217,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds() protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds()
{ {
if (!ValidationHandler.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress)) if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress))
{ {
return; return;
} }

2
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs

@ -95,7 +95,7 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(ResourceOwnerPasswordValidationContext context) protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds(ResourceOwnerPasswordValidationContext context)
{ {
if (!ValidationHandler.IsValidEmailAddress(context.UserName)) if (!ValidationHelper.IsValidEmailAddress(context.UserName))
{ {
return; return;
} }

2
npm/ng-packs/apps/dev-app/src/index.html

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>DevApp</title> <title>ABP Dev</title>
<base href="/" /> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" /> <link rel="icon" type="image/x-icon" href="favicon.ico" />

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

@ -6,7 +6,7 @@
<div class="abp-account-container"> <div class="abp-account-container">
<div <div
*ngIf="(enableLocalLogin$ | async) !== 'False'; else disableLocalLoginTemplate" *ngIf="enableLocalLogin; else disableLocalLoginTemplate"
class="card mt-3 shadow-sm rounded" class="card mt-3 shadow-sm rounded"
> >
<div class="card-body p-5"> <div class="card-body p-5">

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

@ -1,8 +1,7 @@
import { Component, Input, TemplateRef } from '@angular/core'; import { ConfigState, takeUntilDestroy } from '@abp/ng.core';
import { Component, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { Store } from '@ngxs/store';
import { Account } from '../../models/account'; import { Account } from '../../models/account';
import { Select } from '@ngxs/store';
import { ConfigState } from '@abp/ng.core';
import { Observable } from 'rxjs';
@Component({ @Component({
selector: 'abp-auth-wrapper', selector: 'abp-auth-wrapper',
@ -10,13 +9,31 @@ import { Observable } from 'rxjs';
exportAs: 'abpAuthWrapper', exportAs: 'abpAuthWrapper',
}) })
export class AuthWrapperComponent export class AuthWrapperComponent
implements Account.AuthWrapperComponentInputs, Account.AuthWrapperComponentOutputs { implements
@Select(ConfigState.getSetting('Abp.Account.EnableLocalLogin')) Account.AuthWrapperComponentInputs,
enableLocalLogin$: Observable<'True' | 'False'>; Account.AuthWrapperComponentOutputs,
OnInit,
OnDestroy {
@Input() @Input()
readonly mainContentRef: TemplateRef<any>; readonly mainContentRef: TemplateRef<any>;
@Input() @Input()
readonly cancelContentRef: TemplateRef<any>; readonly cancelContentRef: TemplateRef<any>;
enableLocalLogin = true;
constructor(private store: Store) {}
ngOnInit() {
this.store
.select(ConfigState.getSetting('Abp.Account.EnableLocalLogin'))
.pipe(takeUntilDestroy(this))
.subscribe(value => {
if (value) {
this.enableLocalLogin = value.toLowerCase() !== 'false';
}
});
}
ngOnDestroy() {}
} }

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

@ -72,7 +72,7 @@ export class ChangePasswordComponent
required, required,
validatePassword(passwordRulesArr), validatePassword(passwordRulesArr),
minLength(requiredLength), minLength(requiredLength),
maxLength(32), maxLength(128),
], ],
}, },
], ],
@ -83,7 +83,7 @@ export class ChangePasswordComponent
required, required,
validatePassword(passwordRulesArr), validatePassword(passwordRulesArr),
minLength(requiredLength), minLength(requiredLength),
maxLength(32), maxLength(128),
], ],
}, },
], ],

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

@ -12,7 +12,7 @@
</abp-auth-wrapper> </abp-auth-wrapper>
<ng-template #mainContentRef> <ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4> <h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong> <strong *ngIf="isSelfRegistrationEnabled">
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }} {{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register">{{ <a class="text-decoration-none" routerLink="/account/register">{{
'AbpAccount::Register' | abpLocalization 'AbpAccount::Register' | abpLocalization

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

@ -1,15 +1,12 @@
import { GetAppConfiguration, ConfigState, SessionState } from '@abp/ng.core'; import { AuthService, SetRemember, ConfigState } from '@abp/ng.core';
import { Component, Inject, Optional } from '@angular/core'; import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Navigate } from '@ngxs/router-plugin';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { from, throwError } from 'rxjs'; import { throwError } from 'rxjs';
import { Options } from '../../models/options'; import { catchError, finalize } from 'rxjs/operators';
import { ToasterService } from '@abp/ng.theme.shared';
import { catchError, finalize, switchMap, tap } from 'rxjs/operators';
import snq from 'snq'; import snq from 'snq';
import { HttpHeaders } from '@angular/common/http';
const { maxLength, minLength, required } = Validators; const { maxLength, minLength, required } = Validators;
@ -17,47 +14,43 @@ const { maxLength, minLength, required } = Validators;
selector: 'abp-login', selector: 'abp-login',
templateUrl: './login.component.html', templateUrl: './login.component.html',
}) })
export class LoginComponent { export class LoginComponent implements OnInit {
form: FormGroup; form: FormGroup;
inProgress: boolean; inProgress: boolean;
isSelfRegistrationEnabled = true;
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private oauthService: OAuthService, private oauthService: OAuthService,
private store: Store, private store: Store,
private toasterService: ToasterService, private toasterService: ToasterService,
@Optional() @Inject('ACCOUNT_OPTIONS') private options: Options, private authService: AuthService,
) { ) {}
this.oauthService.configure(this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig);
this.oauthService.loadDiscoveryDocument(); ngOnInit() {
this.isSelfRegistrationEnabled =
(
(this.store.selectSnapshot(
ConfigState.getSetting('Abp.Account.IsSelfRegistrationEnabled'),
) as string) || ''
).toLowerCase() !== 'false';
this.form = this.fb.group({ this.form = this.fb.group({
username: ['', [required, maxLength(255)]], username: ['', [required, maxLength(255)]],
password: ['', [required, maxLength(32)]], password: ['', [required, maxLength(128)]],
remember: [false], remember: [false],
}); });
} }
onSubmit() { onSubmit() {
if (this.form.invalid) return; if (this.form.invalid) return;
// this.oauthService.setStorage(this.form.value.remember ? localStorage : sessionStorage);
this.inProgress = true; this.inProgress = true;
const tenant = this.store.selectSnapshot(SessionState.getTenant); this.authService
from( .login(this.form.get('username').value, this.form.get('password').value)
this.oauthService.fetchTokenUsingPasswordFlow(
this.form.get('username').value,
this.form.get('password').value,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
),
)
.pipe( .pipe(
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => {
const redirectUrl = snq(() => window.history.state).redirectUrl || (this.options || {}).redirectUrl || '/';
this.store.dispatch(new Navigate([redirectUrl]));
}),
catchError(err => { catchError(err => {
this.toasterService.error( this.toasterService.error(
snq(() => err.error.error_description) || snq(() => err.error.error_description) ||
@ -69,6 +62,8 @@ export class LoginComponent {
}), }),
finalize(() => (this.inProgress = false)), finalize(() => (this.inProgress = false)),
) )
.subscribe(); .subscribe(() => {
this.store.dispatch(new SetRemember(this.form.get('remember').value));
});
} }
} }

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

@ -16,7 +16,13 @@
'AbpAccount::Login' | abpLocalization 'AbpAccount::Login' | abpLocalization
}}</a> }}</a>
</strong> </strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4"> <form
*ngIf="isSelfRegistrationEnabled"
[formGroup]="form"
(ngSubmit)="onSubmit()"
validateOnSubmit
class="mt-4"
>
<div class="form-group"> <div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label <label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span ><span> * </span

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

@ -1,4 +1,4 @@
import { ConfigState, GetAppConfiguration, ABP, SessionState } from '@abp/ng.core'; import { ConfigState, GetAppConfiguration, ABP, SessionState, AuthService } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared'; import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -23,20 +23,36 @@ export class RegisterComponent implements OnInit {
inProgress: boolean; inProgress: boolean;
isSelfRegistrationEnabled = true;
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private accountService: AccountService, private accountService: AccountService,
private oauthService: OAuthService, private oauthService: OAuthService,
private store: Store, private store: Store,
private toasterService: ToasterService, private toasterService: ToasterService,
) { private authService: AuthService,
this.oauthService.configure( ) {}
this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig,
);
this.oauthService.loadDiscoveryDocument();
}
ngOnInit() { ngOnInit() {
this.isSelfRegistrationEnabled =
(
this.store.selectSnapshot(
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;
}
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot( const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
ConfigState.getSettings('Identity.Password'), ConfigState.getSettings('Identity.Password'),
); );
@ -67,7 +83,7 @@ export class RegisterComponent implements OnInit {
username: ['', [required, maxLength(255)]], username: ['', [required, maxLength(255)]],
password: [ password: [
'', '',
[required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(32)], [required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(128)],
], ],
email: ['', [required, email]], email: ['', [required, email]],
}); });
@ -85,25 +101,10 @@ export class RegisterComponent implements OnInit {
appName: 'Angular', appName: 'Angular',
} as RegisterRequest; } as RegisterRequest;
const tenant = this.store.selectSnapshot(SessionState.getTenant);
this.accountService this.accountService
.register(newUser) .register(newUser)
.pipe( .pipe(
switchMap(() => switchMap(() => this.authService.login(newUser.userName, newUser.password)),
from(
this.oauthService.fetchTokenUsingPasswordFlow(
newUser.userName,
newUser.password,
new HttpHeaders({
...(tenant && tenant.id && { __tenant: tenant.id }),
}),
),
),
),
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => this.store.dispatch(new Navigate(['/']))),
take(1),
catchError(err => { catchError(err => {
this.toasterService.error( this.toasterService.error(
snq(() => err.error.error_description) || snq(() => err.error.error_description) ||

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

@ -8,3 +8,11 @@ export class SetTenant {
static readonly type = '[Session] Set Tenant'; static readonly type = '[Session] Set Tenant';
constructor(public payload: ABP.BasicItem) {} constructor(public payload: ABP.BasicItem) {}
} }
export class ModifyOpenedTabCount {
static readonly type = '[Session] Modify Opened Tab Count';
constructor(public operation: 'increase' | 'decrease') {}
}
export class SetRemember {
static readonly type = '[Session] Set Remember';
constructor(public payload: boolean) {}
}

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

@ -6,7 +6,7 @@ import { RouterModule } from '@angular/router';
import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; import { NgxsRouterPluginModule } from '@ngxs/router-plugin';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin'; import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store'; import { NgxsModule, NGXS_PLUGINS } from '@ngxs/store';
import { OAuthModule } from 'angular-oauth2-oidc'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { AbstractNgModelComponent } from './abstracts/ng-model.component'; import { AbstractNgModelComponent } from './abstracts/ng-model.component';
import { DynamicLayoutComponent } from './components/dynamic-layout.component'; import { DynamicLayoutComponent } from './components/dynamic-layout.component';
import { RouterOutletComponent } from './components/router-outlet.component'; import { RouterOutletComponent } from './components/router-outlet.component';
@ -34,12 +34,15 @@ import { ReplaceableComponentsState } from './states/replaceable-components.stat
import { InitDirective } from './directives/init.directive'; import { InitDirective } from './directives/init.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive'; import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
export function storageFactory(): OAuthStorage {
return localStorage;
}
@NgModule({ @NgModule({
imports: [ imports: [
NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]), NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]),
NgxsRouterPluginModule.forRoot(), NgxsRouterPluginModule.forRoot(),
NgxsStoragePluginModule.forRoot({ key: ['SessionState'] }), NgxsStoragePluginModule.forRoot({ key: ['SessionState'] }),
OAuthModule.forRoot(), OAuthModule,
CommonModule, CommonModule,
HttpClientModule, HttpClientModule,
FormsModule, FormsModule,
@ -127,6 +130,8 @@ export class CoreModule {
deps: [Injector], deps: [Injector],
useFactory: localeInitializer, useFactory: localeInitializer,
}, },
...OAuthModule.forRoot().providers,
{ provide: OAuthStorage, useFactory: storageFactory },
], ],
}; };
} }

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

@ -4,5 +4,12 @@ export namespace Session {
export interface State { export interface State {
language: string; language: string;
tenant: ABP.BasicItem; tenant: ABP.BasicItem;
sessionDetail: SessionDetail;
}
export interface SessionDetail {
openedTabCount: number;
lastExitTime: number;
remember: boolean;
} }
} }

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

@ -0,0 +1,51 @@
import { HttpHeaders } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { Navigate } from '@ngxs/router-plugin';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { from, Observable } from 'rxjs';
import { switchMap, tap, take } from 'rxjs/operators';
import snq from 'snq';
import { GetAppConfiguration } from '../actions/config.actions';
import { SessionState } from '../states/session.state';
import { RestService } from './rest.service';
import { ConfigState } from '../states/config.state';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(
private rest: RestService,
private oAuthService: OAuthService,
private store: Store,
@Optional() @Inject('ACCOUNT_OPTIONS') private options: any,
) {}
login(username: string, password: string): Observable<any> {
const tenant = this.store.selectSnapshot(SessionState.getTenant);
this.oAuthService.configure(
this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig,
);
return from(this.oAuthService.loadDiscoveryDocument()).pipe(
switchMap(() =>
from(
this.oAuthService.fetchTokenUsingPasswordFlow(
username,
password,
new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
),
),
),
switchMap(() => this.store.dispatch(new GetAppConfiguration())),
tap(() => {
const redirectUrl =
snq(() => window.history.state.redirectUrl) || (this.options || {}).redirectUrl || '/';
this.store.dispatch(new Navigate([redirectUrl]));
}),
take(1),
);
}
}

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

@ -1,4 +1,5 @@
export * from './application-configuration.service'; export * from './application-configuration.service';
export * from './auth.service';
export * from './config-state.service'; export * from './config-state.service';
export * from './lazy-load.service'; export * from './lazy-load.service';
export * from './localization.service'; export * from './localization.service';

20
npm/ng-packs/packages/core/src/lib/services/session-state.service.ts

@ -1,8 +1,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import {
SetLanguage,
SetRemember,
SetTenant,
ModifyOpenedTabCount,
} from '../actions/session.actions';
import { SessionState } from '../states'; import { SessionState } from '../states';
import { ABP } from '../models';
import { SetLanguage, SetTenant } from '../actions';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -18,6 +22,10 @@ export class SessionStateService {
return this.store.selectSnapshot(SessionState.getTenant); return this.store.selectSnapshot(SessionState.getTenant);
} }
getSessionDetail() {
return this.store.selectSnapshot(SessionState.getSessionDetail);
}
dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) { dispatchSetLanguage(...args: ConstructorParameters<typeof SetLanguage>) {
return this.store.dispatch(new SetLanguage(...args)); return this.store.dispatch(new SetLanguage(...args));
} }
@ -25,4 +33,12 @@ export class SessionStateService {
dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) { dispatchSetTenant(...args: ConstructorParameters<typeof SetTenant>) {
return this.store.dispatch(new SetTenant(...args)); return this.store.dispatch(new SetTenant(...args));
} }
dispatchSetRemember(...args: ConstructorParameters<typeof SetRemember>) {
return this.store.dispatch(new SetRemember(...args));
}
dispatchModifyOpenedTabCount(...args: ConstructorParameters<typeof ModifyOpenedTabCount>) {
return this.store.dispatch(new ModifyOpenedTabCount(...args));
}
} }

106
npm/ng-packs/packages/core/src/lib/states/session.state.ts

@ -1,14 +1,29 @@
import { Action, Selector, State, StateContext } from '@ngxs/store'; import {
import { from } from 'rxjs'; Action,
import { switchMap } from 'rxjs/operators'; Selector,
State,
StateContext,
Store,
NgxsOnInit,
Actions,
ofActionSuccessful,
} from '@ngxs/store';
import { from, fromEvent } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { GetAppConfiguration } from '../actions/config.actions'; import { GetAppConfiguration } from '../actions/config.actions';
import { SetLanguage, SetTenant } from '../actions/session.actions'; import {
SetLanguage,
SetTenant,
ModifyOpenedTabCount,
SetRemember,
} from '../actions/session.actions';
import { ABP, Session } from '../models'; import { ABP, Session } from '../models';
import { LocalizationService } from '../services/localization.service'; import { LocalizationService } from '../services/localization.service';
import { OAuthService } from 'angular-oauth2-oidc';
@State<Session.State>({ @State<Session.State>({
name: 'SessionState', name: 'SessionState',
defaults: {} as Session.State, defaults: { sessionDetail: { openedTabCount: 0 } } as Session.State,
}) })
export class SessionState { export class SessionState {
@Selector() @Selector()
@ -21,7 +36,42 @@ export class SessionState {
return tenant; return tenant;
} }
constructor(private localizationService: LocalizationService) {} @Selector()
static getSessionDetail({ sessionDetail }: Session.State): Session.SessionDetail {
return sessionDetail;
}
constructor(
private localizationService: LocalizationService,
private oAuthService: OAuthService,
private store: Store,
private actions: Actions,
) {
actions
.pipe(ofActionSuccessful(GetAppConfiguration))
.pipe(take(1))
.subscribe(() => {
const { sessionDetail } = this.store.selectSnapshot(SessionState) || { sessionDetail: {} };
const fiveMinutesBefore = new Date().valueOf() - 5 * 60 * 1000;
if (
sessionDetail.lastExitTime &&
sessionDetail.openedTabCount === 0 &&
this.oAuthService.hasValidAccessToken() &&
sessionDetail.remember === false &&
sessionDetail.lastExitTime < fiveMinutesBefore
) {
this.oAuthService.logOut();
}
this.store.dispatch(new ModifyOpenedTabCount('increase'));
fromEvent(window, 'beforeunload').subscribe(() => {
this.store.dispatch(new ModifyOpenedTabCount('decrease'));
});
});
}
@Action(SetLanguage) @Action(SetLanguage)
setLanguage({ patchState, dispatch }: StateContext<Session.State>, { payload }: SetLanguage) { setLanguage({ patchState, dispatch }: StateContext<Session.State>, { payload }: SetLanguage) {
@ -40,4 +90,48 @@ export class SessionState {
tenant: payload, tenant: payload,
}); });
} }
@Action(SetRemember)
setRemember(
{ getState, patchState }: StateContext<Session.State>,
{ payload: remember }: SetRemember,
) {
const { sessionDetail } = getState();
patchState({
sessionDetail: {
...sessionDetail,
remember,
},
});
}
@Action(ModifyOpenedTabCount)
modifyOpenedTabCount(
{ getState, patchState }: StateContext<Session.State>,
{ operation }: ModifyOpenedTabCount,
) {
// tslint:disable-next-line: prefer-const
let { openedTabCount, lastExitTime, ...detail } =
getState().sessionDetail || ({ openedTabCount: 0 } as Session.SessionDetail);
if (operation === 'increase') {
openedTabCount++;
} else if (operation === 'decrease') {
openedTabCount--;
lastExitTime = new Date().valueOf();
}
if (!openedTabCount || openedTabCount < 0) {
openedTabCount = 0;
}
patchState({
sessionDetail: {
openedTabCount,
lastExitTime,
...detail,
},
});
}
} }

2
npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts

@ -10,6 +10,7 @@ import { ABP } from '../models';
import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from '../plugins'; import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from '../plugins';
import { ConfigState } from '../states'; import { ConfigState } from '../states';
import { addAbpRoutes } from '../utils'; import { addAbpRoutes } from '../utils';
import { OAuthModule } from 'angular-oauth2-oidc';
addAbpRoutes([ addAbpRoutes([
{ {
@ -323,6 +324,7 @@ describe('ConfigPlugin', () => {
service: ConfigPlugin, service: ConfigPlugin,
imports: [ imports: [
CoreModule, CoreModule,
OAuthModule.forRoot(),
NgxsModule.forRoot([]), NgxsModule.forRoot([]),
RouterTestingModule.withRoutes([ RouterTestingModule.withRoutes([
{ {

6
npm/ng-packs/packages/core/src/lib/tests/session-state.service.spec.ts

@ -3,13 +3,17 @@ import { SessionStateService } from '../services/session-state.service';
import { SessionState } from '../states/session.state'; import { SessionState } from '../states/session.state';
import { Store } from '@ngxs/store'; import { Store } from '@ngxs/store';
import * as SessionActions from '../actions'; import * as SessionActions from '../actions';
import { OAuthService } from 'angular-oauth2-oidc';
describe('SessionStateService', () => { describe('SessionStateService', () => {
let service: SessionStateService; let service: SessionStateService;
let spectator: SpectatorService<SessionStateService>; let spectator: SpectatorService<SessionStateService>;
let store: SpyObject<Store>; let store: SpyObject<Store>;
const createService = createServiceFactory({ service: SessionStateService, mocks: [Store] }); const createService = createServiceFactory({
service: SessionStateService,
mocks: [Store, OAuthService],
});
beforeEach(() => { beforeEach(() => {
spectator = createService(); spectator = createService();
service = spectator.service; service = spectator.service;

10
npm/ng-packs/packages/core/src/lib/tests/session.state.spec.ts

@ -1,9 +1,11 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Session } from '../models/session'; import { Session } from '../models/session';
import { LocalizationService } from '../services'; import { LocalizationService, AuthService } from '../services';
import { SessionState } from '../states'; import { SessionState } from '../states';
import { GetAppConfiguration } from '../actions/config.actions'; import { GetAppConfiguration } from '../actions/config.actions';
import { of } from 'rxjs'; import { of, Subject } from 'rxjs';
import { Store, Actions } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
export class DummyClass {} export class DummyClass {}
@ -18,12 +20,12 @@ describe('SessionState', () => {
const createService = createServiceFactory({ const createService = createServiceFactory({
service: DummyClass, service: DummyClass,
mocks: [LocalizationService], mocks: [LocalizationService, Store, Actions, OAuthService],
}); });
beforeEach(() => { beforeEach(() => {
spectator = createService(); spectator = createService();
state = new SessionState(spectator.get(LocalizationService)); state = new SessionState(spectator.get(LocalizationService), null, null, new Subject());
}); });
describe('#getLanguage', () => { describe('#getLanguage', () => {

2
npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts

@ -144,7 +144,7 @@ export class UsersComponent implements OnInit {
const passwordValidators = [ const passwordValidators = [
validatePassword(this.passwordRulesArr), validatePassword(this.passwordRulesArr),
Validators.minLength(this.requiredPasswordLength), Validators.minLength(this.requiredPasswordLength),
Validators.maxLength(32), Validators.maxLength(128),
]; ];
this.form.addControl('password', new FormControl('', [...passwordValidators])); this.form.addControl('password', new FormControl('', [...passwordValidators]));

2
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -3,6 +3,7 @@ import { Component } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { ConfirmationService } from '../services/confirmation.service'; import { ConfirmationService } from '../services/confirmation.service';
import { ThemeSharedModule } from '../theme-shared.module'; import { ThemeSharedModule } from '../theme-shared.module';
@ -22,6 +23,7 @@ describe('ConfirmationService', () => {
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: DummyComponent, component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule], imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
mocks: [OAuthService],
}); });
beforeEach(() => { beforeEach(() => {

56
npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts

@ -9,8 +9,12 @@ import { ThemeSharedModule } from '../theme-shared.module';
import { MessageService } from 'primeng/components/common/messageservice'; import { MessageService } from 'primeng/components/common/messageservice';
import { RouterError, RouterDataResolved } from '@ngxs/router-plugin'; import { RouterError, RouterDataResolved } from '@ngxs/router-plugin';
import { NavigationError, ResolveEnd } from '@angular/router'; import { NavigationError, ResolveEnd } from '@angular/router';
import { OAuthModule, OAuthService } from 'angular-oauth2-oidc';
@Component({ selector: 'abp-dummy', template: 'dummy works! <abp-confirmation></abp-confirmation>' }) @Component({
selector: 'abp-dummy',
template: 'dummy works! <abp-confirmation></abp-confirmation>',
})
class DummyComponent { class DummyComponent {
constructor(public errorHandler: ErrorHandler) {} constructor(public errorHandler: ErrorHandler) {}
} }
@ -21,6 +25,7 @@ describe('ErrorHandler', () => {
const createComponent = createRoutingFactory({ const createComponent = createRoutingFactory({
component: DummyComponent, component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([])], imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot([])],
mocks: [OAuthService],
stubsEnabled: false, stubsEnabled: false,
routes: [ routes: [
{ path: '', component: DummyComponent }, { path: '', component: DummyComponent },
@ -38,33 +43,53 @@ describe('ErrorHandler', () => {
it('should display the error component when server error occurs', () => { it('should display the error component when server error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 500 }))); store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 500 })));
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError500.title); expect(document.querySelector('.error-template')).toHaveText(
expect(document.querySelector('.error-details')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError500.details); DEFAULT_ERROR_MESSAGES.defaultError500.title,
);
expect(document.querySelector('.error-details')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError500.details,
);
}); });
it('should display the error component when authorize error occurs', () => { it('should display the error component when authorize error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 403 }))); store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 403 })));
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError403.title); expect(document.querySelector('.error-template')).toHaveText(
expect(document.querySelector('.error-details')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError403.details); DEFAULT_ERROR_MESSAGES.defaultError403.title,
);
expect(document.querySelector('.error-details')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError403.details,
);
}); });
it('should display the error component when unknown error occurs', () => { it('should display the error component when unknown error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' }))); store.dispatch(
expect(document.querySelector('.error-template')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.title); new RestOccurError(new HttpErrorResponse({ status: 0, statusText: 'Unknown Error' })),
);
expect(document.querySelector('.error-template')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError.title,
);
}); });
it('should display the confirmation when not found error occurs', () => { it('should display the confirmation when not found error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 404 }))); store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 404 })));
spectator.detectChanges(); spectator.detectChanges();
expect(spectator.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.title); expect(spectator.query('.abp-confirm-summary')).toHaveText(
expect(spectator.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError404.details); DEFAULT_ERROR_MESSAGES.defaultError404.title,
);
expect(spectator.query('.abp-confirm-body')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError404.details,
);
}); });
it('should display the confirmation when default error occurs', () => { it('should display the confirmation when default error occurs', () => {
store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 412 }))); store.dispatch(new RestOccurError(new HttpErrorResponse({ status: 412 })));
spectator.detectChanges(); spectator.detectChanges();
expect(spectator.query('.abp-confirm-summary')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.title); expect(spectator.query('.abp-confirm-summary')).toHaveText(
expect(spectator.query('.abp-confirm-body')).toHaveText(DEFAULT_ERROR_MESSAGES.defaultError.details); DEFAULT_ERROR_MESSAGES.defaultError.title,
);
expect(spectator.query('.abp-confirm-body')).toHaveText(
DEFAULT_ERROR_MESSAGES.defaultError.details,
);
}); });
it('should display the confirmation when authenticated error occurs', async () => { it('should display the confirmation when authenticated error occurs', async () => {
@ -110,7 +135,8 @@ describe('ErrorHandler', () => {
@Component({ @Component({
selector: 'abp-dummy-error', selector: 'abp-dummy-error',
template: '<p>{{errorStatus}}</p><button id="close-dummy" (click)="destroy$.next()">Close</button>', template:
'<p>{{errorStatus}}</p><button id="close-dummy" (click)="destroy$.next()">Close</button>',
}) })
class DummyErrorComponent { class DummyErrorComponent {
errorStatus; errorStatus;
@ -130,12 +156,16 @@ describe('ErrorHandler with custom error component', () => {
imports: [ imports: [
CoreModule, CoreModule,
ThemeSharedModule.forRoot({ ThemeSharedModule.forRoot({
httpErrorConfig: { errorScreen: { component: DummyErrorComponent, forWhichErrors: [401, 403, 404, 500] } }, httpErrorConfig: {
errorScreen: { component: DummyErrorComponent, forWhichErrors: [401, 403, 404, 500] },
},
}), }),
NgxsModule.forRoot([]), NgxsModule.forRoot([]),
ErrorModule, ErrorModule,
], ],
mocks: [OAuthService],
stubsEnabled: false, stubsEnabled: false,
routes: [ routes: [
{ path: '', component: DummyComponent }, { path: '', component: DummyComponent },
{ path: 'account/login', component: RouterOutletComponent }, { path: 'account/login', component: RouterOutletComponent },

2
npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts

@ -5,6 +5,7 @@ import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
import { ToasterService } from '../services/toaster.service'; import { ToasterService } from '../services/toaster.service';
import { ThemeSharedModule } from '../theme-shared.module'; import { ThemeSharedModule } from '../theme-shared.module';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({ @Component({
selector: 'abp-dummy', selector: 'abp-dummy',
@ -22,6 +23,7 @@ describe('ToasterService', () => {
const createComponent = createComponentFactory({ const createComponent = createComponentFactory({
component: DummyComponent, component: DummyComponent,
imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule], imports: [CoreModule, ThemeSharedModule.forRoot(), NgxsModule.forRoot(), RouterTestingModule],
mocks: [OAuthService],
}); });
beforeEach(() => { beforeEach(() => {

Loading…
Cancel
Save