mirror of https://github.com/abpframework/abp
commit
3dd275b468
@ -0,0 +1,3 @@
|
||||
<h1> @abp/ng.account </h1>
|
||||
|
||||
[docs.abp.io](https://docs.abp.io)
|
||||
@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/account/config",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { ACCOUNT_ROUTE_PROVIDERS } from './providers/route.provider';
|
||||
|
||||
@NgModule()
|
||||
export class AccountConfigModule {
|
||||
static forRoot(): ModuleWithProviders<AccountConfigModule> {
|
||||
return {
|
||||
ngModule: AccountConfigModule,
|
||||
providers: [ACCOUNT_ROUTE_PROVIDERS],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './route-names';
|
||||
@ -0,0 +1,6 @@
|
||||
export const enum eAccountRouteNames {
|
||||
Account = 'AbpAccount::Menu:Account',
|
||||
Login = 'AbpAccount::Login',
|
||||
Register = 'AbpAccount::Register',
|
||||
ManageProfile = 'AbpAccount::ManageYourProfile',
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './route.provider';
|
||||
@ -0,0 +1,39 @@
|
||||
import { eLayoutType, RoutesService } from '@abp/ng.core';
|
||||
import { APP_INITIALIZER } from '@angular/core';
|
||||
import { eAccountRouteNames } from '../enums/route-names';
|
||||
|
||||
export const ACCOUNT_ROUTE_PROVIDERS = [
|
||||
{ provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
|
||||
];
|
||||
|
||||
export function configureRoutes(routes: RoutesService) {
|
||||
return () => {
|
||||
routes.add([
|
||||
{
|
||||
path: '/account',
|
||||
name: eAccountRouteNames.Account,
|
||||
invisible: true,
|
||||
layout: eLayoutType.application,
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
path: '/account/login',
|
||||
name: eAccountRouteNames.Login,
|
||||
parentName: eAccountRouteNames.Account,
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
path: '/account/register',
|
||||
name: eAccountRouteNames.Register,
|
||||
parentName: eAccountRouteNames.Account,
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
path: '/account/manage-profile',
|
||||
name: eAccountRouteNames.ManageProfile,
|
||||
parentName: eAccountRouteNames.Account,
|
||||
order: 3,
|
||||
},
|
||||
]);
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export * from './account-config.module';
|
||||
export * from './enums';
|
||||
export * from './providers';
|
||||
@ -0,0 +1,6 @@
|
||||
const jestConfig = require('../../jest.config');
|
||||
|
||||
module.exports = {
|
||||
...jestConfig,
|
||||
name: 'account',
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/account",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
},
|
||||
"whitelistedNonPeerDependencies": ["@abp/ng.theme.shared"]
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
entryPoints: {
|
||||
'.': {},
|
||||
'./config': {}
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@abp/ng.account",
|
||||
"version": "4.3.0",
|
||||
"homepage": "https://abp.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/abpframework/abp.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@abp/ng.theme.shared": "~3.3.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import {
|
||||
AuthGuard,
|
||||
DynamicLayoutComponent,
|
||||
ReplaceableComponents,
|
||||
ReplaceableRouteContainerComponent,
|
||||
} from '@abp/ng.core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
|
||||
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
|
||||
import { RegisterComponent } from './components/register/register.component';
|
||||
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
||||
import { eAccountComponents } from './enums/components';
|
||||
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'login' },
|
||||
{
|
||||
path: '',
|
||||
component: DynamicLayoutComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: AuthWrapperComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
component: ReplaceableRouteContainerComponent,
|
||||
canActivate: [AuthenticationFlowGuard],
|
||||
data: {
|
||||
replaceableComponent: {
|
||||
key: eAccountComponents.Login,
|
||||
defaultComponent: LoginComponent,
|
||||
} as ReplaceableComponents.RouteData<LoginComponent>,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
component: ReplaceableRouteContainerComponent,
|
||||
canActivate: [AuthenticationFlowGuard],
|
||||
data: {
|
||||
replaceableComponent: {
|
||||
key: eAccountComponents.Register,
|
||||
defaultComponent: RegisterComponent,
|
||||
} as ReplaceableComponents.RouteData<RegisterComponent>,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'forgot-password',
|
||||
component: ReplaceableRouteContainerComponent,
|
||||
canActivate: [AuthenticationFlowGuard],
|
||||
data: {
|
||||
replaceableComponent: {
|
||||
key: eAccountComponents.ForgotPassword,
|
||||
defaultComponent: ForgotPasswordComponent,
|
||||
} as ReplaceableComponents.RouteData<ForgotPasswordComponent>,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
component: ReplaceableRouteContainerComponent,
|
||||
canActivate: [AuthenticationFlowGuard],
|
||||
data: {
|
||||
replaceableComponent: {
|
||||
key: eAccountComponents.ResetPassword,
|
||||
defaultComponent: ResetPasswordComponent,
|
||||
} as ReplaceableComponents.RouteData<ResetPasswordComponent>,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'manage-profile',
|
||||
component: ReplaceableRouteContainerComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
replaceableComponent: {
|
||||
key: eAccountComponents.ManageProfile,
|
||||
defaultComponent: ManageProfileComponent,
|
||||
} as ReplaceableComponents.RouteData<ManageProfileComponent>,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AccountRoutingModule {}
|
||||
@ -0,0 +1,63 @@
|
||||
import { CoreModule, LazyModuleFactory } from '@abp/ng.core';
|
||||
import { ThemeSharedModule } from '@abp/ng.theme.shared';
|
||||
import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
|
||||
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgxValidateCoreModule } from '@ngx-validate/core';
|
||||
import { AccountRoutingModule } from './account-routing.module';
|
||||
import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
|
||||
import { ChangePasswordComponent } from './components/change-password/change-password.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
|
||||
import { PersonalSettingsComponent } from './components/personal-settings/personal-settings.component';
|
||||
import { RegisterComponent } from './components/register/register.component';
|
||||
import { TenantBoxComponent } from './components/tenant-box/tenant-box.component';
|
||||
import { AccountConfigOptions } from './models/config-options';
|
||||
import { ACCOUNT_CONFIG_OPTIONS } from './tokens/config-options.token';
|
||||
import { accountConfigOptionsFactory } from './utils/factory-utils';
|
||||
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
|
||||
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
|
||||
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
||||
|
||||
const declarations = [
|
||||
AuthWrapperComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
TenantBoxComponent,
|
||||
ChangePasswordComponent,
|
||||
ManageProfileComponent,
|
||||
PersonalSettingsComponent,
|
||||
ForgotPasswordComponent,
|
||||
ResetPasswordComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...declarations],
|
||||
imports: [
|
||||
CoreModule,
|
||||
AccountRoutingModule,
|
||||
ThemeSharedModule,
|
||||
NgbDropdownModule,
|
||||
NgxValidateCoreModule,
|
||||
],
|
||||
exports: [...declarations],
|
||||
})
|
||||
export class AccountModule {
|
||||
static forChild(options = {} as AccountConfigOptions): ModuleWithProviders<AccountModule> {
|
||||
return {
|
||||
ngModule: AccountModule,
|
||||
providers: [
|
||||
AuthenticationFlowGuard,
|
||||
{ provide: ACCOUNT_CONFIG_OPTIONS, useValue: options },
|
||||
{
|
||||
provide: 'ACCOUNT_OPTIONS',
|
||||
useFactory: accountConfigOptionsFactory,
|
||||
deps: [ACCOUNT_CONFIG_OPTIONS],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static forLazy(options = {} as AccountConfigOptions): NgModuleFactory<AccountModule> {
|
||||
return new LazyModuleFactory(AccountModule.forChild(options));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<div class="row">
|
||||
<div class="mx-auto col col-md-5">
|
||||
<ng-container *ngIf="(isMultiTenancyEnabled$ | async) && multiTenancy.isTenantBoxVisible">
|
||||
<abp-tenant-box *abpReplaceableTemplate="{ componentKey: tenantBoxKey }"></abp-tenant-box>
|
||||
</ng-container>
|
||||
|
||||
<div class="abp-account-container">
|
||||
<div
|
||||
*ngIf="enableLocalLogin$ | async; else disableLocalLoginTemplate"
|
||||
class="card mt-3 shadow-sm rounded"
|
||||
>
|
||||
<div class="card-body p-5">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #disableLocalLoginTemplate>
|
||||
<div class="alert alert-warning">
|
||||
<strong>{{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}</strong>
|
||||
{{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }}
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,28 @@
|
||||
import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core';
|
||||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { eAccountComponents } from '../../enums/components';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-auth-wrapper',
|
||||
templateUrl: './auth-wrapper.component.html',
|
||||
exportAs: 'abpAuthWrapper',
|
||||
providers: [SubscriptionService],
|
||||
})
|
||||
export class AuthWrapperComponent {
|
||||
isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled');
|
||||
|
||||
get enableLocalLogin$(): Observable<boolean> {
|
||||
return this.configState
|
||||
.getSetting$('Abp.Account.EnableLocalLogin')
|
||||
.pipe(map(value => value?.toLowerCase() !== 'false'));
|
||||
}
|
||||
|
||||
tenantBoxKey = eAccountComponents.TenantBox;
|
||||
|
||||
constructor(
|
||||
public readonly multiTenancy: MultiTenancyService,
|
||||
private configState: ConfigStateService,
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit>
|
||||
<div *ngIf="!hideCurrentPassword" class="form-group">
|
||||
<label for="current-password">{{
|
||||
'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization
|
||||
}}</label
|
||||
><span> * </span
|
||||
><input
|
||||
type="password"
|
||||
id="current-password"
|
||||
class="form-control"
|
||||
formControlName="password"
|
||||
autofocus
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input
|
||||
type="password"
|
||||
id="new-password"
|
||||
class="form-control"
|
||||
formControlName="newPassword"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm-new-password">{{
|
||||
'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization
|
||||
}}</label
|
||||
><span> * </span
|
||||
><input
|
||||
type="password"
|
||||
id="confirm-new-password"
|
||||
class="form-control"
|
||||
formControlName="repeatNewPassword"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<abp-button
|
||||
iconClass="fa fa-check"
|
||||
buttonClass="btn btn-primary color-white"
|
||||
buttonType="submit"
|
||||
[loading]="inProgress"
|
||||
[disabled]="form?.invalid"
|
||||
>{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
|
||||
>
|
||||
</form>
|
||||
@ -0,0 +1,99 @@
|
||||
import { Profile, ProfileService } from '@abp/ng.core';
|
||||
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
|
||||
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { comparePasswords, Validation } from '@ngx-validate/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import snq from 'snq';
|
||||
import { Account } from '../../models/account';
|
||||
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
|
||||
|
||||
const { required } = Validators;
|
||||
|
||||
const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
|
||||
|
||||
@Component({
|
||||
selector: 'abp-change-password-form',
|
||||
templateUrl: './change-password.component.html',
|
||||
exportAs: 'abpChangePasswordForm',
|
||||
})
|
||||
export class ChangePasswordComponent
|
||||
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress: boolean;
|
||||
|
||||
hideCurrentPassword: boolean;
|
||||
|
||||
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
|
||||
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
|
||||
|
||||
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
|
||||
};
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private injector: Injector,
|
||||
private toasterService: ToasterService,
|
||||
private profileService: ProfileService,
|
||||
private manageProfileState: ManageProfileStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword;
|
||||
|
||||
const passwordValidations = getPasswordValidators(this.injector);
|
||||
|
||||
this.form = this.fb.group(
|
||||
{
|
||||
password: ['', required],
|
||||
newPassword: [
|
||||
'',
|
||||
{
|
||||
validators: [required, ...passwordValidations],
|
||||
},
|
||||
],
|
||||
repeatNewPassword: [
|
||||
'',
|
||||
{
|
||||
validators: [required, ...passwordValidations],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
validators: [comparePasswords(PASSWORD_FIELDS)],
|
||||
},
|
||||
);
|
||||
|
||||
if (this.hideCurrentPassword) this.form.removeControl('password');
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid) return;
|
||||
this.inProgress = true;
|
||||
this.profileService
|
||||
.changePassword({
|
||||
...(!this.hideCurrentPassword && { currentPassword: this.form.get('password').value }),
|
||||
newPassword: this.form.get('newPassword').value,
|
||||
})
|
||||
.pipe(finalize(() => (this.inProgress = false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.form.reset();
|
||||
this.toasterService.success('AbpAccount::PasswordChangedMessage', '', {
|
||||
life: 5000,
|
||||
});
|
||||
|
||||
if (this.hideCurrentPassword) {
|
||||
this.hideCurrentPassword = false;
|
||||
this.form.addControl('password', new FormControl('', [required]));
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
this.toasterService.error(
|
||||
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<h4>{{ 'AbpAccount::ForgotPassword' | abpLocalization }}</h4>
|
||||
|
||||
<form
|
||||
*ngIf="!isEmailSent; else emailSentTemplate"
|
||||
[formGroup]="form"
|
||||
(ngSubmit)="onSubmit()"
|
||||
validateOnSubmit
|
||||
>
|
||||
<p>{{ 'AbpAccount::SendPasswordResetLink_Information' | abpLocalization }}</p>
|
||||
<div class="form-group">
|
||||
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
|
||||
><span> * </span>
|
||||
<input type="email" id="input-email-address" class="form-control" formControlName="email" />
|
||||
</div>
|
||||
<abp-button
|
||||
class="d-block"
|
||||
buttonClass="mt-2 mb-3 btn btn-primary btn-block"
|
||||
[loading]="inProgress"
|
||||
buttonType="submit"
|
||||
[disabled]="form?.invalid"
|
||||
>
|
||||
{{ 'AbpAccount::Submit' | abpLocalization }}
|
||||
</abp-button>
|
||||
<a routerLink="/account/login"
|
||||
><i class="fa fa-long-arrow-left mr-1"></i>{{ 'AbpAccount::Login' | abpLocalization }}</a
|
||||
>
|
||||
</form>
|
||||
|
||||
<ng-template #emailSentTemplate>
|
||||
<p>
|
||||
{{ 'AbpAccount::PasswordResetMailSentMessage' | abpLocalization }}
|
||||
</p>
|
||||
|
||||
<a routerLink="/account/login">
|
||||
<button class="d-block mt-2 mb-3 btn btn-primary btn-block">
|
||||
<i class="fa fa-long-arrow-left mr-1"></i>
|
||||
{{ 'AbpAccount::BackToLogin' | abpLocalization }}
|
||||
</button>
|
||||
</a>
|
||||
</ng-template>
|
||||
@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { AccountService } from '../../proxy/account/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-forgot-password',
|
||||
templateUrl: 'forgot-password.component.html',
|
||||
})
|
||||
export class ForgotPasswordComponent {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress: boolean;
|
||||
|
||||
isEmailSent = false;
|
||||
|
||||
constructor(private fb: FormBuilder, private accountService: AccountService) {
|
||||
this.form = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid) return;
|
||||
|
||||
this.inProgress = true;
|
||||
|
||||
this.accountService
|
||||
.sendPasswordResetCode({ email: this.form.get('email').value, appName: 'Angular' })
|
||||
.pipe(finalize(() => (this.inProgress = false)))
|
||||
.subscribe(() => {
|
||||
this.isEmailSent = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export * from './change-password/change-password.component';
|
||||
export * from './forgot-password/forgot-password.component';
|
||||
export * from './login/login.component';
|
||||
export * from './manage-profile/manage-profile.component';
|
||||
export * from './register/register.component';
|
||||
export * from './personal-settings/personal-settings.component';
|
||||
export * from './reset-password/reset-password.component';
|
||||
export * from './tenant-box/tenant-box.component';
|
||||
@ -0,0 +1,60 @@
|
||||
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
|
||||
<strong *ngIf="isSelfRegistrationEnabled">
|
||||
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
|
||||
<a class="text-decoration-none" routerLink="/account/register" queryParamsHandling="preserve">{{
|
||||
'AbpAccount::Register' | abpLocalization
|
||||
}}</a>
|
||||
</strong>
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
|
||||
<div class="form-group">
|
||||
<label for="login-input-user-name-or-email-address">{{
|
||||
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
|
||||
}}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="login-input-user-name-or-email-address"
|
||||
formControlName="username"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
|
||||
<input
|
||||
class="form-control"
|
||||
type="password"
|
||||
id="login-input-password"
|
||||
formControlName="password"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="custom-checkbox custom-control mb-2" for="login-input-remember-me">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="login-input-remember-me"
|
||||
formControlName="rememberMe"
|
||||
/>
|
||||
{{ 'AbpAccount::RememberMe' | abpLocalization }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-right col">
|
||||
<a routerLink="/account/forgot-password">{{
|
||||
'AbpAccount::ForgotPassword' | abpLocalization
|
||||
}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<abp-button
|
||||
[loading]="inProgress"
|
||||
buttonType="submit"
|
||||
name="Action"
|
||||
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
|
||||
>
|
||||
{{ 'AbpAccount::Login' | abpLocalization }}
|
||||
</abp-button>
|
||||
</form>
|
||||
@ -0,0 +1,80 @@
|
||||
import { ConfigStateService, AuthService } from '@abp/ng.core';
|
||||
import { ToasterService } from '@abp/ng.theme.shared';
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { throwError } from 'rxjs';
|
||||
import { catchError, finalize } from 'rxjs/operators';
|
||||
import snq from 'snq';
|
||||
import { eAccountComponents } from '../../enums/components';
|
||||
import { getRedirectUrl } from '../../utils/auth-utils';
|
||||
|
||||
const { maxLength, required } = Validators;
|
||||
|
||||
@Component({
|
||||
selector: 'abp-login',
|
||||
templateUrl: './login.component.html',
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress: boolean;
|
||||
|
||||
isSelfRegistrationEnabled = true;
|
||||
|
||||
authWrapperKey = eAccountComponents.AuthWrapper;
|
||||
|
||||
constructor(
|
||||
protected injector: Injector,
|
||||
protected fb: FormBuilder,
|
||||
protected toasterService: ToasterService,
|
||||
protected authService: AuthService,
|
||||
protected configState: ConfigStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.init();
|
||||
this.buildForm();
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.isSelfRegistrationEnabled =
|
||||
(
|
||||
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') as string) || ''
|
||||
).toLowerCase() !== 'false';
|
||||
}
|
||||
|
||||
protected buildForm() {
|
||||
this.form = this.fb.group({
|
||||
username: ['', [required, maxLength(255)]],
|
||||
password: ['', [required, maxLength(128)]],
|
||||
rememberMe: [false],
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid) return;
|
||||
|
||||
this.inProgress = true;
|
||||
|
||||
const { username, password, rememberMe } = this.form.value;
|
||||
|
||||
const redirectUrl = getRedirectUrl(this.injector);
|
||||
|
||||
this.authService
|
||||
.login({ username, password, rememberMe, redirectUrl })
|
||||
.pipe(
|
||||
catchError(err => {
|
||||
this.toasterService.error(
|
||||
snq(() => err.error.error_description) ||
|
||||
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
|
||||
'Error',
|
||||
{ life: 7000 },
|
||||
);
|
||||
return throwError(err);
|
||||
}),
|
||||
finalize(() => (this.inProgress = false)),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<div id="AbpContentToolbar"></div>
|
||||
|
||||
<div class="card border-0 shadow-sm min-h-400" [abpLoading]="!(profile$ | async)">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3">
|
||||
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist">
|
||||
<li
|
||||
*ngIf="!hideChangePasswordTab && (profile$ | async)"
|
||||
class="nav-item"
|
||||
(click)="selectedTab = 0"
|
||||
>
|
||||
<a
|
||||
class="nav-link"
|
||||
[ngClass]="{ active: selectedTab === 0 }"
|
||||
role="tab"
|
||||
href="javascript:void(0)"
|
||||
>{{ 'AbpUi::ChangePassword' | abpLocalization }}</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item mb-2" (click)="selectedTab = 1">
|
||||
<a
|
||||
class="nav-link"
|
||||
[ngClass]="{ active: selectedTab === 1 }"
|
||||
role="tab"
|
||||
href="javascript:void(0)"
|
||||
>{{ 'AbpAccount::PersonalSettings' | abpLocalization }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="profile$ | async" class="col-12 col-md-9">
|
||||
<div class="tab-content" *ngIf="selectedTab === 0" [@fadeIn]>
|
||||
<div class="tab-pane active" role="tabpanel">
|
||||
<h4>
|
||||
{{ 'AbpIdentity::ChangePassword' | abpLocalization }}
|
||||
<hr />
|
||||
</h4>
|
||||
<abp-change-password-form
|
||||
*abpReplaceableTemplate="{
|
||||
componentKey: changePasswordKey
|
||||
}"
|
||||
></abp-change-password-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]>
|
||||
<div class="tab-pane active" role="tabpanel">
|
||||
<h4>
|
||||
{{ 'AbpIdentity::PersonalSettings' | abpLocalization }}
|
||||
<hr />
|
||||
</h4>
|
||||
<abp-personal-settings-form
|
||||
*abpReplaceableTemplate="{
|
||||
componentKey: personalSettingsKey
|
||||
}"
|
||||
></abp-personal-settings-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,45 @@
|
||||
import { Profile, ProfileService } from '@abp/ng.core';
|
||||
import { fadeIn } from '@abp/ng.theme.shared';
|
||||
import { transition, trigger, useAnimation } from '@angular/animations';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { eAccountComponents } from '../../enums/components';
|
||||
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-manage-profile',
|
||||
templateUrl: './manage-profile.component.html',
|
||||
animations: [trigger('fadeIn', [transition(':enter', useAnimation(fadeIn))])],
|
||||
styles: [
|
||||
`
|
||||
.min-h-400 {
|
||||
min-height: 400px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class ManageProfileComponent implements OnInit {
|
||||
selectedTab = 0;
|
||||
|
||||
changePasswordKey = eAccountComponents.ChangePassword;
|
||||
|
||||
personalSettingsKey = eAccountComponents.PersonalSettings;
|
||||
|
||||
profile$ = this.manageProfileState.getProfile$();
|
||||
|
||||
hideChangePasswordTab: boolean;
|
||||
|
||||
constructor(
|
||||
protected profileService: ProfileService,
|
||||
protected manageProfileState: ManageProfileStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.profileService.get().subscribe(profile => {
|
||||
this.manageProfileState.setProfile(profile);
|
||||
if (profile.isExternal) {
|
||||
this.hideChangePasswordTab = true;
|
||||
this.selectedTab = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<form validateOnSubmit *ngIf="form" [formGroup]="form" (ngSubmit)="submit()">
|
||||
<div class="form-group">
|
||||
<label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input
|
||||
type="text"
|
||||
id="username"
|
||||
class="form-control"
|
||||
formControlName="userName"
|
||||
autofocus
|
||||
(keydown.space)="$event.preventDefault()"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label
|
||||
><input type="text" id="name" class="form-control" formControlName="name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label
|
||||
><input type="text" id="surname" class="form-control" formControlName="surname" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input type="text" id="email-address" class="form-control" formControlName="email" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label
|
||||
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" />
|
||||
</div>
|
||||
<abp-button
|
||||
buttonType="submit"
|
||||
iconClass="fa fa-check"
|
||||
buttonClass="btn btn-primary color-white"
|
||||
[loading]="inProgress"
|
||||
[disabled]="form?.invalid"
|
||||
>
|
||||
{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
|
||||
>
|
||||
</form>
|
||||
@ -0,0 +1,58 @@
|
||||
import { ProfileService } from '@abp/ng.core';
|
||||
import { ToasterService } from '@abp/ng.theme.shared';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { Account } from '../../models/account';
|
||||
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
|
||||
|
||||
const { maxLength, required, email } = Validators;
|
||||
|
||||
@Component({
|
||||
selector: 'abp-personal-settings-form',
|
||||
templateUrl: './personal-settings.component.html',
|
||||
exportAs: 'abpPersonalSettingsForm',
|
||||
})
|
||||
export class PersonalSettingsComponent
|
||||
implements
|
||||
OnInit,
|
||||
Account.PersonalSettingsComponentInputs,
|
||||
Account.PersonalSettingsComponentOutputs {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress: boolean;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private toasterService: ToasterService,
|
||||
private profileService: ProfileService,
|
||||
private manageProfileState: ManageProfileStateService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.buildForm();
|
||||
}
|
||||
|
||||
buildForm() {
|
||||
const profile = this.manageProfileState.getProfile();
|
||||
this.form = this.fb.group({
|
||||
userName: [profile.userName, [required, maxLength(256)]],
|
||||
email: [profile.email, [required, email, maxLength(256)]],
|
||||
name: [profile.name || '', [maxLength(64)]],
|
||||
surname: [profile.surname || '', [maxLength(64)]],
|
||||
phoneNumber: [profile.phoneNumber || '', [maxLength(16)]],
|
||||
});
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.form.invalid) return;
|
||||
this.inProgress = true;
|
||||
this.profileService
|
||||
.update(this.form.value)
|
||||
.pipe(finalize(() => (this.inProgress = false)))
|
||||
.subscribe(profile => {
|
||||
this.manageProfileState.setProfile(profile);
|
||||
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<h4>{{ 'AbpAccount::Register' | abpLocalization }}</h4>
|
||||
<strong>
|
||||
{{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
|
||||
<a class="text-decoration-none" routerLink="/account/login">{{
|
||||
'AbpAccount::Login' | abpLocalization
|
||||
}}</a>
|
||||
</strong>
|
||||
<form
|
||||
*ngIf="isSelfRegistrationEnabled"
|
||||
[formGroup]="form"
|
||||
(ngSubmit)="onSubmit()"
|
||||
validateOnSubmit
|
||||
class="mt-4"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input
|
||||
autofocus
|
||||
type="text"
|
||||
id="input-user-name"
|
||||
class="form-control"
|
||||
formControlName="username"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input type="email" id="input-email-address" class="form-control" formControlName="email" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
|
||||
><span> * </span
|
||||
><input
|
||||
type="password"
|
||||
id="input-password"
|
||||
class="form-control"
|
||||
formControlName="password"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<abp-button
|
||||
[loading]="inProgress"
|
||||
buttonType="submit"
|
||||
name="Action"
|
||||
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
|
||||
>
|
||||
{{ 'AbpAccount::Register' | abpLocalization }}
|
||||
</abp-button>
|
||||
</form>
|
||||
@ -0,0 +1,102 @@
|
||||
import { AuthService, ConfigStateService } from '@abp/ng.core';
|
||||
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { throwError } from 'rxjs';
|
||||
import { catchError, finalize, switchMap } from 'rxjs/operators';
|
||||
import snq from 'snq';
|
||||
import { eAccountComponents } from '../../enums/components';
|
||||
import { AccountService } from '../../proxy/account/account.service';
|
||||
import { RegisterDto } from '../../proxy/account/models';
|
||||
import { getRedirectUrl } from '../../utils/auth-utils';
|
||||
const { maxLength, required, email } = Validators;
|
||||
|
||||
@Component({
|
||||
selector: 'abp-register',
|
||||
templateUrl: './register.component.html',
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress: boolean;
|
||||
|
||||
isSelfRegistrationEnabled = true;
|
||||
|
||||
authWrapperKey = eAccountComponents.AuthWrapper;
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
protected accountService: AccountService,
|
||||
protected configState: ConfigStateService,
|
||||
protected toasterService: ToasterService,
|
||||
protected authService: AuthService,
|
||||
protected injector: Injector,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.init();
|
||||
this.buildForm();
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.isSelfRegistrationEnabled =
|
||||
(this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') || '').toLowerCase() !==
|
||||
'false';
|
||||
|
||||
if (!this.isSelfRegistrationEnabled) {
|
||||
this.toasterService.warn(
|
||||
{
|
||||
key: 'AbpAccount::SelfRegistrationDisabledMessage',
|
||||
defaultValue: 'Self registration is disabled.',
|
||||
},
|
||||
null,
|
||||
{ life: 10000 },
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected buildForm() {
|
||||
this.form = this.fb.group({
|
||||
username: ['', [required, maxLength(255)]],
|
||||
password: ['', [required, ...getPasswordValidators(this.injector)]],
|
||||
email: ['', [required, email]],
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid) return;
|
||||
|
||||
this.inProgress = true;
|
||||
|
||||
const newUser = {
|
||||
userName: this.form.get('username').value,
|
||||
password: this.form.get('password').value,
|
||||
emailAddress: this.form.get('email').value,
|
||||
appName: 'Angular',
|
||||
} as RegisterDto;
|
||||
|
||||
this.accountService
|
||||
.register(newUser)
|
||||
.pipe(
|
||||
switchMap(() =>
|
||||
this.authService.login({
|
||||
username: newUser.userName,
|
||||
password: newUser.password,
|
||||
redirectUrl: getRedirectUrl(this.injector),
|
||||
}),
|
||||
),
|
||||
catchError(err => {
|
||||
this.toasterService.error(
|
||||
snq(() => err.error.error_description) ||
|
||||
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
|
||||
'Error',
|
||||
{ life: 7000 },
|
||||
);
|
||||
return throwError(err);
|
||||
}),
|
||||
finalize(() => (this.inProgress = false)),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<h4>{{ 'AbpAccount::ResetPassword' | abpLocalization }}</h4>
|
||||
|
||||
<form
|
||||
*ngIf="!isPasswordReset; else passwordResetTemplate"
|
||||
[formGroup]="form"
|
||||
[mapErrorsFn]="mapErrorsFn"
|
||||
(ngSubmit)="onSubmit()"
|
||||
validateOnSubmit
|
||||
>
|
||||
<p>{{ 'AbpAccount::ResetPassword_Information' | abpLocalization }}</p>
|
||||
<div class="form-group">
|
||||
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
|
||||
><span> * </span>
|
||||
<input type="password" id="input-password" class="form-control" formControlName="password" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="input-confirm-password">{{ 'AbpAccount::ConfirmPassword' | abpLocalization }}</label
|
||||
><span> * </span>
|
||||
<input
|
||||
type="password"
|
||||
id="input-confirm-password"
|
||||
class="form-control"
|
||||
formControlName="confirmPassword"
|
||||
/>
|
||||
</div>
|
||||
<button class="mr-2 btn btn-secondary" type="button" routerLink="/account/login">
|
||||
{{ 'AbpAccount::Cancel' | abpLocalization }}
|
||||
</button>
|
||||
<abp-button
|
||||
buttonType="submit"
|
||||
buttonClass="mr-2 btn btn-primary"
|
||||
[loading]="inProgress"
|
||||
(click)="onSubmit()"
|
||||
>
|
||||
{{ 'AbpAccount::Submit' | abpLocalization }}
|
||||
</abp-button>
|
||||
</form>
|
||||
|
||||
<ng-template #passwordResetTemplate>
|
||||
<p>
|
||||
{{ 'AbpAccount::YourPasswordIsSuccessfullyReset' | abpLocalization }}
|
||||
</p>
|
||||
|
||||
<a routerLink="/account/login">
|
||||
<button class="d-block mt-2 mb-3 btn btn-primary">
|
||||
{{ 'AbpAccount::BackToLogin' | abpLocalization }}
|
||||
</button>
|
||||
</a>
|
||||
</ng-template>
|
||||
@ -0,0 +1,70 @@
|
||||
import { getPasswordValidators } from '@abp/ng.theme.shared';
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { comparePasswords, Validation } from '@ngx-validate/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { AccountService } from '../../proxy/account/account.service';
|
||||
|
||||
const PASSWORD_FIELDS = ['password', 'confirmPassword'];
|
||||
|
||||
@Component({
|
||||
selector: 'abp-reset-password',
|
||||
templateUrl: './reset-password.component.html',
|
||||
})
|
||||
export class ResetPasswordComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
|
||||
inProgress = false;
|
||||
|
||||
isPasswordReset = false;
|
||||
|
||||
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
|
||||
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
|
||||
|
||||
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
|
||||
};
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private accountService: AccountService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private injector: Injector,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(({ userId, resetToken }) => {
|
||||
if (!userId || !resetToken) this.router.navigateByUrl('/account/login');
|
||||
|
||||
this.form = this.fb.group(
|
||||
{
|
||||
userId: [userId, [Validators.required]],
|
||||
resetToken: [resetToken, [Validators.required]],
|
||||
password: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
|
||||
confirmPassword: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
|
||||
},
|
||||
{
|
||||
validators: [comparePasswords(PASSWORD_FIELDS)],
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid || this.inProgress) return;
|
||||
|
||||
this.inProgress = true;
|
||||
|
||||
this.accountService
|
||||
.resetPassword({
|
||||
userId: this.form.get('userId').value,
|
||||
resetToken: this.form.get('resetToken').value,
|
||||
password: this.form.get('password').value,
|
||||
})
|
||||
.pipe(finalize(() => (this.inProgress = false)))
|
||||
.subscribe(() => {
|
||||
this.isPasswordReset = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<ng-container *ngIf="(currentTenant$ | async) || {} as currentTenant">
|
||||
<div class="card shadow-sm rounded mb-3">
|
||||
<div class="card-body px-5">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<span style="font-size: 0.8em;" class="text-uppercase text-muted">{{
|
||||
'AbpUiMultiTenancy::Tenant' | abpLocalization
|
||||
}}</span
|
||||
><br />
|
||||
<h6 class="m-0 d-inline-block">
|
||||
<i>{{ currentTenant.name || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}</i>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a
|
||||
id="AbpTenantSwitchLink"
|
||||
href="javascript:void(0);"
|
||||
class="btn btn-sm mt-3 btn-outline-primary"
|
||||
(click)="onSwitch()"
|
||||
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy">
|
||||
<ng-template #abpHeader>
|
||||
<h5>Switch Tenant</h5>
|
||||
</ng-template>
|
||||
<ng-template #abpBody>
|
||||
<form (ngSubmit)="save()">
|
||||
<div class="mt-2">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ 'AbpUiMultiTenancy::Name' | abpLocalization }}</label>
|
||||
<input
|
||||
[(ngModel)]="name"
|
||||
type="text"
|
||||
id="name"
|
||||
name="tenant"
|
||||
class="form-control"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
<p>{{ 'AbpUiMultiTenancy::SwitchTenantHint' | abpLocalization }}</p>
|
||||
</div>
|
||||
</form>
|
||||
</ng-template>
|
||||
<ng-template #abpFooter>
|
||||
<button #abpClose type="button" class="btn btn-secondary">
|
||||
{{ 'AbpTenantManagement::Cancel' | abpLocalization }}
|
||||
</button>
|
||||
<abp-button
|
||||
type="abp-button"
|
||||
iconClass="fa fa-check"
|
||||
(click)="save()"
|
||||
[disabled]="currentTenant?.name === name"
|
||||
>
|
||||
<span>{{ 'AbpTenantManagement::Save' | abpLocalization }}</span>
|
||||
</abp-button>
|
||||
</ng-template>
|
||||
</abp-modal>
|
||||
</ng-container>
|
||||
@ -0,0 +1,73 @@
|
||||
import {
|
||||
AbpApplicationConfigurationService,
|
||||
AbpTenantService,
|
||||
ConfigStateService,
|
||||
CurrentTenantDto,
|
||||
SessionStateService,
|
||||
} from '@abp/ng.core';
|
||||
import { ToasterService } from '@abp/ng.theme.shared';
|
||||
import { Component } from '@angular/core';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { Account } from '../../models/account';
|
||||
|
||||
@Component({
|
||||
selector: 'abp-tenant-box',
|
||||
templateUrl: './tenant-box.component.html',
|
||||
})
|
||||
export class TenantBoxComponent
|
||||
implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
|
||||
currentTenant$ = this.sessionState.getTenant$();
|
||||
|
||||
name: string;
|
||||
|
||||
isModalVisible: boolean;
|
||||
|
||||
modalBusy: boolean;
|
||||
|
||||
constructor(
|
||||
private toasterService: ToasterService,
|
||||
private tenantService: AbpTenantService,
|
||||
private sessionState: SessionStateService,
|
||||
private configState: ConfigStateService,
|
||||
private appConfigService: AbpApplicationConfigurationService,
|
||||
) {}
|
||||
|
||||
onSwitch() {
|
||||
const tenant = this.sessionState.getTenant();
|
||||
this.name = tenant?.name;
|
||||
this.isModalVisible = true;
|
||||
}
|
||||
|
||||
save() {
|
||||
if (!this.name) {
|
||||
this.setTenant(null);
|
||||
this.isModalVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalBusy = true;
|
||||
this.tenantService
|
||||
.findTenantByName(this.name, {})
|
||||
.pipe(finalize(() => (this.modalBusy = false)))
|
||||
.subscribe(({ success, tenantId: id, ...tenant }) => {
|
||||
if (!success) {
|
||||
this.showError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTenant({ ...tenant, id, isAvailable: true });
|
||||
this.isModalVisible = false;
|
||||
});
|
||||
}
|
||||
|
||||
private setTenant(tenant: CurrentTenantDto) {
|
||||
this.sessionState.setTenant(tenant);
|
||||
this.appConfigService.get().subscribe(res => this.configState.setState(res));
|
||||
}
|
||||
|
||||
private showError() {
|
||||
this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
|
||||
messageLocalizationParams: [this.name],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
export const enum eAccountComponents {
|
||||
Login = 'Account.LoginComponent',
|
||||
Register = 'Account.RegisterComponent',
|
||||
ForgotPassword = 'Account.ForgotPasswordComponent',
|
||||
ResetPassword = 'Account.ResetPasswordComponent',
|
||||
ManageProfile = 'Account.ManageProfileComponent',
|
||||
TenantBox = 'Account.TenantBoxComponent',
|
||||
AuthWrapper = 'Account.AuthWrapperComponent',
|
||||
ChangePassword = 'Account.ChangePasswordComponent',
|
||||
PersonalSettings = 'Account.PersonalSettingsComponent',
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './components';
|
||||
@ -0,0 +1,15 @@
|
||||
import { AuthService } from '@abp/ng.core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationFlowGuard implements CanActivate {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
canActivate() {
|
||||
if (this.authService.isInternalAuth) return true;
|
||||
|
||||
this.authService.navigateToLogin();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './authentication-flow.guard';
|
||||
@ -0,0 +1,12 @@
|
||||
import { TemplateRef } from '@angular/core';
|
||||
|
||||
export namespace Account {
|
||||
//tslint:disable
|
||||
export interface TenantBoxComponentInputs {}
|
||||
export interface TenantBoxComponentOutputs {}
|
||||
export interface PersonalSettingsComponentInputs {}
|
||||
export interface PersonalSettingsComponentOutputs {}
|
||||
export interface ChangePasswordComponentInputs {}
|
||||
export interface ChangePasswordComponentOutputs {}
|
||||
// tslint:enable
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
export interface AccountConfigOptions {
|
||||
redirectUrl?: string;
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './account';
|
||||
export * from './config-options';
|
||||
@ -0,0 +1,17 @@
|
||||
# Proxy Generation Output
|
||||
|
||||
This directory includes the output of the latest proxy generation.
|
||||
The files and folders in it will be overwritten when proxy generation is run again.
|
||||
Therefore, please do not place your own content in this folder.
|
||||
|
||||
In addition, `generate-proxy.json` works like a lock file.
|
||||
It includes information used by the proxy generator, so please do not delete or modify it.
|
||||
|
||||
Finally, the name of the files and folders should not be changed for two reasons:
|
||||
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content.
|
||||
- ABP Suite generates files which include imports from this folder.
|
||||
|
||||
> **Important Notice:** If you are building a module and are planning to publish to npm,
|
||||
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case,
|
||||
> please make sure you export files directly and not from barrel exports. In other words,
|
||||
> do not include index.ts exports in your public-api.ts exports.
|
||||
@ -0,0 +1,37 @@
|
||||
import type { RegisterDto, ResetPasswordDto, SendPasswordResetCodeDto } from './models';
|
||||
import { RestService } from '@abp/ng.core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import type { IdentityUserDto } from '../identity/models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AccountService {
|
||||
apiName = 'AbpAccount';
|
||||
|
||||
register = (input: RegisterDto) =>
|
||||
this.restService.request<any, IdentityUserDto>({
|
||||
method: 'POST',
|
||||
url: '/api/account/register',
|
||||
body: input,
|
||||
},
|
||||
{ apiName: this.apiName });
|
||||
|
||||
resetPassword = (input: ResetPasswordDto) =>
|
||||
this.restService.request<any, void>({
|
||||
method: 'POST',
|
||||
url: '/api/account/reset-password',
|
||||
body: input,
|
||||
},
|
||||
{ apiName: this.apiName });
|
||||
|
||||
sendPasswordResetCode = (input: SendPasswordResetCodeDto) =>
|
||||
this.restService.request<any, void>({
|
||||
method: 'POST',
|
||||
url: '/api/account/send-password-reset-code',
|
||||
body: input,
|
||||
},
|
||||
{ apiName: this.apiName });
|
||||
|
||||
constructor(private restService: RestService) {}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './account.service';
|
||||
export * from './models';
|
||||
@ -0,0 +1,20 @@
|
||||
|
||||
export interface RegisterDto {
|
||||
userName: string;
|
||||
emailAddress: string;
|
||||
password: string;
|
||||
appName: string;
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
userId?: string;
|
||||
resetToken: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface SendPasswordResetCodeDto {
|
||||
email: string;
|
||||
appName: string;
|
||||
returnUrl?: string;
|
||||
returnUrlHash?: string;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
export * from './models';
|
||||
@ -0,0 +1,15 @@
|
||||
import type { ExtensibleFullAuditedEntityDto } from '@abp/ng.core';
|
||||
|
||||
export interface IdentityUserDto extends ExtensibleFullAuditedEntityDto<string> {
|
||||
tenantId?: string;
|
||||
userName?: string;
|
||||
name?: string;
|
||||
surname?: string;
|
||||
email?: string;
|
||||
emailConfirmed: boolean;
|
||||
phoneNumber?: string;
|
||||
phoneNumberConfirmed: boolean;
|
||||
lockoutEnabled: boolean;
|
||||
lockoutEnd?: string;
|
||||
concurrencyStamp?: string;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import * as Account from './account';
|
||||
import * as Identity from './identity';
|
||||
export { Account, Identity };
|
||||
@ -0,0 +1 @@
|
||||
export * from './manage-profile.state.service';
|
||||
@ -0,0 +1,28 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { InternalStore, Profile } from '@abp/ng.core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface ManageProfileState {
|
||||
profile: Profile.Response;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ManageProfileStateService {
|
||||
private readonly store = new InternalStore({} as ManageProfileState);
|
||||
|
||||
get createOnUpdateStream() {
|
||||
return this.store.sliceUpdate;
|
||||
}
|
||||
|
||||
getProfile$(): Observable<Profile.Response> {
|
||||
return this.store.sliceState(state => state.profile);
|
||||
}
|
||||
|
||||
getProfile(): Profile.Response {
|
||||
return this.store.state.profile;
|
||||
}
|
||||
|
||||
setProfile(profile: Profile.Response) {
|
||||
this.store.patch({ profile });
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { AccountConfigOptions } from '../models/config-options';
|
||||
|
||||
export const ACCOUNT_CONFIG_OPTIONS = new InjectionToken<AccountConfigOptions>(
|
||||
'ACCOUNT_CONFIG_OPTIONS',
|
||||
);
|
||||
@ -0,0 +1 @@
|
||||
export * from './config-options.token';
|
||||
@ -0,0 +1,9 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ACCOUNT_CONFIG_OPTIONS } from '../tokens';
|
||||
|
||||
export function getRedirectUrl(injector: Injector) {
|
||||
const route = injector.get(ActivatedRoute);
|
||||
const options = injector.get(ACCOUNT_CONFIG_OPTIONS);
|
||||
return route.snapshot.queryParams.returnUrl || options.redirectUrl || '/';
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { AccountConfigOptions } from '../models/config-options';
|
||||
|
||||
export function accountConfigOptionsFactory(options: AccountConfigOptions) {
|
||||
return {
|
||||
redirectUrl: '/',
|
||||
...options,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './auth-utils';
|
||||
export * from './factory-utils';
|
||||
@ -0,0 +1,9 @@
|
||||
export * from './lib/account.module';
|
||||
export * from './lib/components';
|
||||
export * from './lib/enums';
|
||||
export * from './lib/guards';
|
||||
export * from './lib/models';
|
||||
export * from './lib/services';
|
||||
export * from './lib/tokens';
|
||||
export * from './lib/proxy/account';
|
||||
export * from './lib/proxy/identity';
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.prod.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": ["dom", "es2018"]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true,
|
||||
"strictMetadataEmit": true,
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true,
|
||||
"enableResourceInlining": true
|
||||
},
|
||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.prod.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["**/*.spec.ts", "**/*.d.ts"]
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"abp",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"abp",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue