Browse Source

Merge pull request #17569 from abpframework/issue/volo-17495

Add tenant not found token
pull/17783/head
Masum ULU 3 years ago
committed by GitHub
parent
commit
d56b6a15f0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      docs/en/UI/Angular/Multi-Tenancy.md
  2. 3
      npm/ng-packs/packages/core/src/lib/tokens/index.ts
  3. 6
      npm/ng-packs/packages/core/src/lib/tokens/tenant-not-found-by-name.ts
  4. 54
      npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts
  5. 6
      npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts
  6. 1
      npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts
  7. 20
      npm/ng-packs/packages/theme-shared/src/lib/providers/tenant-not-found.provider.ts
  8. 9
      npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts

31
docs/en/UI/Angular/Multi-Tenancy.md

@ -125,6 +125,37 @@ After this replacement, the app will use the following URLs:
The app sends the `__tenant` header that contains the current tenant id on each request. The app sends the `__tenant` header that contains the current tenant id on each request.
### Handling Domain-based Tenant not found
When the application is initialized and a domain-based tenant is active, it makes a request for to validate the tenant. If it cannot be located, the Angular application calls the function. The function displays an error message, but the functionality can be customized by injecting the `TENANT_NOT_FOUND_BY_NAME` token into the function.
Example:
```ts
import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core';
@NgModule({
imports: [
// removed for clarity
],
providers: [
// removed for clarity
{
provide: TENANT_NOT_FOUND_BY_NAME,
useFactory: function () {
return () => {
window.location.replace('https://abp.io');
};
},
},
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
```
## See Also ## See Also
- [Multi Tenancy in ABP](../../Multi-Tenancy.md) - [Multi Tenancy in ABP](../../Multi-Tenancy.md)

3
npm/ng-packs/packages/core/src/lib/tokens/index.ts

@ -12,4 +12,5 @@ export * from './pipe-to-login.token';
export * from './set-token-response-to-storage.token'; export * from './set-token-response-to-storage.token';
export * from './check-authentication-state'; export * from './check-authentication-state';
export * from './http-context.token'; export * from './http-context.token';
export * from './others-group.token' export * from './others-group.token';
export * from './tenant-not-found-by-name';

6
npm/ng-packs/packages/core/src/lib/tokens/tenant-not-found-by-name.ts

@ -0,0 +1,6 @@
import { InjectionToken } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
export const TENANT_NOT_FOUND_BY_NAME = new InjectionToken<
(HttpErrorResponse: HttpErrorResponse) => void
>('TENANT_NOT_FOUND_BY_NAME');

54
npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts

@ -1,13 +1,14 @@
import { Injector } from '@angular/core'; import { Injector } from '@angular/core';
import clone from 'just-clone'; import clone from 'just-clone';
import { tap } from 'rxjs/operators';
import { Environment } from '../models/environment'; import { Environment } from '../models/environment';
import { FindTenantResultDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models'; import { FindTenantResultDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { EnvironmentService } from '../services/environment.service'; import { EnvironmentService } from '../services/environment.service';
import { MultiTenancyService } from '../services/multi-tenancy.service'; import { MultiTenancyService } from '../services/multi-tenancy.service';
import { createTokenParser } from './string-utils'; import { createTokenParser } from './string-utils';
import { firstValueFrom } from 'rxjs';
import { TENANT_NOT_FOUND_BY_NAME } from '../tokens/tenant-not-found-by-name';
import { HttpErrorResponse } from '@angular/common/http';
const tenancyPlaceholder = '{0}'; const tenancyPlaceholder = '{0}';
@ -27,6 +28,7 @@ function getCurrentTenancyNameFromUrl(tenantKey: string) {
export async function parseTenantFromUrl(injector: Injector) { export async function parseTenantFromUrl(injector: Injector) {
const environmentService = injector.get(EnvironmentService); const environmentService = injector.get(EnvironmentService);
const multiTenancyService = injector.get(MultiTenancyService); const multiTenancyService = injector.get(MultiTenancyService);
const tenantNotFoundHandler = injector.get(TENANT_NOT_FOUND_BY_NAME, null);
const baseUrl = environmentService.getEnvironment()?.application?.baseUrl || ''; const baseUrl = environmentService.getEnvironment()?.application?.baseUrl || '';
const tenancyName = getCurrentTenancyName(baseUrl); const tenancyName = getCurrentTenancyName(baseUrl);
@ -55,21 +57,33 @@ export async function parseTenantFromUrl(injector: Injector) {
* Before this request takes place, we need to replace placeholders aka "{0}". * Before this request takes place, we need to replace placeholders aka "{0}".
*/ */
replaceTenantNameWithinEnvironment(injector, tenancyName); replaceTenantNameWithinEnvironment(injector, tenancyName);
return multiTenancyService
.setTenantByName(tenancyName)
.pipe(tap(setEnvironmentWithDomainTenant))
.toPromise();
} else {
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
const tenantIdFromQueryParams = getCurrentTenancyNameFromUrl(multiTenancyService.tenantKey); const tenant$ = multiTenancyService.setTenantByName(tenancyName);
if (tenantIdFromQueryParams) { try {
return multiTenancyService.setTenantById(tenantIdFromQueryParams).toPromise(); const result = await firstValueFrom(tenant$);
setEnvironmentWithDomainTenant(result);
return Promise.resolve(result);
} catch (httpError: HttpErrorResponse | any) {
if (
httpError instanceof HttpErrorResponse &&
httpError.status === 404 &&
tenantNotFoundHandler
) {
tenantNotFoundHandler(httpError);
}
return Promise.reject();
} }
} }
/**
* If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
*/
replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
const tenantIdFromQueryParams = getCurrentTenancyNameFromUrl(multiTenancyService.tenantKey);
if (tenantIdFromQueryParams) {
const tenantById$ = multiTenancyService.setTenantById(tenantIdFromQueryParams);
return firstValueFrom(tenantById$);
}
return Promise.resolve(); return Promise.resolve();
} }
@ -97,14 +111,20 @@ function replaceTenantNameWithinEnvironment(
); );
} }
if(!environment.oAuthConfig) { if (!environment.oAuthConfig) {
environment.oAuthConfig = {}; environment.oAuthConfig = {};
} }
environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(placeholder, tenancyName); environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(
placeholder,
tenancyName,
);
Object.keys(environment.apis).forEach(api => { Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => { Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = (environment.apis[api][key] || '').replace(placeholder, tenancyName); environment.apis[api][key] = (environment.apis[api][key] || '').replace(
placeholder,
tenancyName,
);
}); });
}); });

6
npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts

@ -1,6 +1,6 @@
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { from, Observable, lastValueFrom } from 'rxjs'; import { from, Observable, lastValueFrom, EMPTY } from 'rxjs';
import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import { AbpAuthResponse, IAuthService, LoginParams } from '@abp/ng.core'; import { AbpAuthResponse, IAuthService, LoginParams } from '@abp/ng.core';
import { AuthFlowStrategy } from '../strategies'; import { AuthFlowStrategy } from '../strategies';
@ -44,6 +44,10 @@ export class AbpOAuthService implements IAuthService {
} }
logout(queryParams?: Params): Observable<any> { logout(queryParams?: Params): Observable<any> {
if(!this.strategy){
return EMPTY
}
return this.strategy.logout(queryParams); return this.strategy.logout(queryParams);
} }

1
npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts

@ -1,2 +1,3 @@
export * from './ng-bootstrap-config.provider'; export * from './ng-bootstrap-config.provider';
export * from './route.provider'; export * from './route.provider';
export * from './tenant-not-found.provider';

20
npm/ng-packs/packages/theme-shared/src/lib/providers/tenant-not-found.provider.ts

@ -0,0 +1,20 @@
import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core';
import { inject, Provider } from '@angular/core';
import { ConfirmationService } from '../services';
import { HttpErrorResponse } from '@angular/common/http';
export const tenantNotFoundProvider: Provider = {
provide: TENANT_NOT_FOUND_BY_NAME,
useFactory: function () {
const confirm = inject(ConfirmationService);
return (response: HttpErrorResponse) => {
const { error } = response.error;
// hide loading donut
const appRoot = document.querySelector('app-root div.donut');
if (appRoot) {
appRoot.remove();
}
confirm.error(error.details, error.message, { hideCancelBtn: true, hideYesBtn: true });
};
},
};

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

@ -40,6 +40,7 @@ import { CardModule } from './components/card/card.module';
import { AbpVisibleDirective } from './directives'; import { AbpVisibleDirective } from './directives';
import { FormInputComponent } from './components/form-input/form-input.component'; import { FormInputComponent } from './components/form-input/form-input.component';
import { FormCheckboxComponent } from './components/checkbox/checkbox.component'; import { FormCheckboxComponent } from './components/checkbox/checkbox.component';
import { tenantNotFoundProvider } from './providers/tenant-not-found.provider';
const declarationsWithExports = [ const declarationsWithExports = [
BreadcrumbComponent, BreadcrumbComponent,
@ -58,7 +59,7 @@ const declarationsWithExports = [
ModalCloseDirective, ModalCloseDirective,
AbpVisibleDirective, AbpVisibleDirective,
FormInputComponent, FormInputComponent,
FormCheckboxComponent FormCheckboxComponent,
]; ];
@NgModule({ @NgModule({
@ -69,7 +70,6 @@ const declarationsWithExports = [
NgbPaginationModule, NgbPaginationModule,
EllipsisModule, EllipsisModule,
CardModule, CardModule,
], ],
declarations: [...declarationsWithExports, HttpErrorWrapperComponent], declarations: [...declarationsWithExports, HttpErrorWrapperComponent],
exports: [ exports: [
@ -77,11 +77,11 @@ const declarationsWithExports = [
EllipsisModule, EllipsisModule,
NgxValidateCoreModule, NgxValidateCoreModule,
CardModule, CardModule,
...declarationsWithExports ...declarationsWithExports,
], ],
providers: [DatePipe], providers: [DatePipe],
}) })
export class BaseThemeSharedModule { } export class BaseThemeSharedModule {}
@NgModule({ @NgModule({
imports: [BaseThemeSharedModule], imports: [BaseThemeSharedModule],
@ -144,6 +144,7 @@ export class ThemeSharedModule {
...(confirmationIcons || {}), ...(confirmationIcons || {}),
}, },
}, },
tenantNotFoundProvider,
], ],
}; };
} }

Loading…
Cancel
Save