Merge pull request #15201 from abpframework/issue/10019-move-oauth-seperated-package

Issue/10019 move oauth seperated package
pull/15239/head
Muhammed Altuğ 3 years ago committed by GitHub
commit 40029b08f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,14 @@
# ABP OAuth Package
The authentication functionality has been moved from @abp/ng.core to @abp/ng.ouath since v7.0.
If your app is version 7.0 or higher, you should include "AbpOAuthModule.forRoot()" in your app.module.ts as an import after "CoreModule.forRoot(...)".
Those abstractions can be found in the @abp/ng-core packages.
- `AuthService` (the class that implements the IAuthService interface).
- `NAVIGATE_TO_MANAGE_PROFILE` Inject token.
- `AuthGuard` (the class that implements the IAuthGuard interface).
Those base classes are overridden by the "AbpOAuthModule" for oAuth.
If you want to make your own authentication system, you must also change these 'abstract' classes.
ApiInterceptor is provided by @abp/ng.oauth. The ApiInterceptor adds the token, accepted-language, and tenant id to the header of the HTTP request.

@ -10,9 +10,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/account"],
"{workspaceRoot}/dist/packages/account"
],
"options": { "options": {
"project": "packages/account/ng-package.json" "project": "packages/account/ng-package.json"
}, },
@ -28,9 +26,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/account"],
"{workspaceRoot}/coverage/packages/account"
],
"options": { "options": {
"jestConfig": "packages/account/jest.config.ts", "jestConfig": "packages/account/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -39,22 +35,13 @@
"lint": { "lint": {
"builder": "@nrwl/linter:eslint", "builder": "@nrwl/linter:eslint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["packages/account/src/**/*.ts", "packages/account/src/**/*.html"]
"packages/account/src/**/*.ts",
"packages/account/src/**/*.html"
]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared", "account-core"]
"core",
"theme-shared",
"account-core"
]
}, },
"account-core": { "account-core": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -65,9 +52,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/account-core"],
"{workspaceRoot}/dist/packages/account-core"
],
"options": { "options": {
"project": "packages/account-core/ng-package.json" "project": "packages/account-core/ng-package.json"
}, },
@ -83,9 +68,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/account-core"],
"{workspaceRoot}/coverage/packages/account-core"
],
"options": { "options": {
"jestConfig": "packages/account-core/jest.config.ts", "jestConfig": "packages/account-core/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -99,16 +82,11 @@
"packages/account-core/src/**/*.html" "packages/account-core/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared"]
"core",
"theme-shared"
]
}, },
"components": { "components": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -119,9 +97,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/components"],
"{workspaceRoot}/dist/packages/components"
],
"options": { "options": {
"project": "packages/components/ng-package.json" "project": "packages/components/ng-package.json"
}, },
@ -137,9 +113,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/components"],
"{workspaceRoot}/coverage/packages/components"
],
"options": { "options": {
"jestConfig": "packages/components/jest.config.ts", "jestConfig": "packages/components/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -153,16 +127,11 @@
"packages/components/src/**/*.html" "packages/components/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared"]
"core",
"theme-shared"
]
}, },
"core": { "core": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -173,9 +142,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/core"],
"{workspaceRoot}/dist/packages/core"
],
"options": { "options": {
"project": "packages/core/ng-package.json" "project": "packages/core/ng-package.json"
}, },
@ -191,9 +158,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/core"],
"{workspaceRoot}/coverage/packages/core"
],
"options": { "options": {
"jestConfig": "packages/core/jest.config.ts", "jestConfig": "packages/core/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -202,14 +167,9 @@
"lint": { "lint": {
"builder": "@nrwl/linter:eslint", "builder": "@nrwl/linter:eslint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["packages/core/src/**/*.ts", "packages/core/src/**/*.html"]
"packages/core/src/**/*.ts",
"packages/core/src/**/*.html"
]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [] "tags": []
@ -223,9 +183,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
"outputs": [ "outputs": ["{options.outputPath}"],
"{options.outputPath}"
],
"options": { "options": {
"outputPath": "dist/apps/dev-app", "outputPath": "dist/apps/dev-app",
"index": "apps/dev-app/src/index.html", "index": "apps/dev-app/src/index.html",
@ -233,14 +191,8 @@
"polyfills": "apps/dev-app/src/polyfills.ts", "polyfills": "apps/dev-app/src/polyfills.ts",
"tsConfig": "apps/dev-app/tsconfig.app.json", "tsConfig": "apps/dev-app/tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": ["chart.js", "js-sha256"],
"chart.js", "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"],
"js-sha256"
],
"assets": [
"apps/dev-app/src/favicon.ico",
"apps/dev-app/src/assets"
],
"styles": [ "styles": [
{ {
"input": "node_modules/bootstrap/dist/css/bootstrap.rtl.min.css", "input": "node_modules/bootstrap/dist/css/bootstrap.rtl.min.css",
@ -341,17 +293,12 @@
"lint": { "lint": {
"builder": "@nrwl/linter:eslint", "builder": "@nrwl/linter:eslint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["apps/dev-app/src/**/*.ts", "apps/dev-app/src/**/*.html"]
"apps/dev-app/src/**/*.ts",
"apps/dev-app/src/**/*.html"
]
} }
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/apps/dev-app"],
"{workspaceRoot}/coverage/apps/dev-app"
],
"options": { "options": {
"jestConfig": "apps/dev-app/jest.config.ts", "jestConfig": "apps/dev-app/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -381,19 +328,13 @@
"lint": { "lint": {
"builder": "@nrwl/linter:eslint", "builder": "@nrwl/linter:eslint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["apps/dev-app-e2e/**/*.{js,ts}"]
"apps/dev-app-e2e/**/*.{js,ts}"
]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["dev-app"]
"dev-app"
]
}, },
"feature-management": { "feature-management": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -404,9 +345,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/feature-management"],
"{workspaceRoot}/dist/packages/feature-management"
],
"options": { "options": {
"project": "packages/feature-management/ng-package.json" "project": "packages/feature-management/ng-package.json"
}, },
@ -422,9 +361,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/feature-management"],
"{workspaceRoot}/coverage/packages/feature-management"
],
"options": { "options": {
"jestConfig": "packages/feature-management/jest.config.ts", "jestConfig": "packages/feature-management/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -438,16 +375,11 @@
"packages/feature-management/src/**/*.html" "packages/feature-management/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared"]
"core",
"theme-shared"
]
}, },
"identity": { "identity": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -458,9 +390,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/identity"],
"{workspaceRoot}/dist/packages/identity"
],
"options": { "options": {
"project": "packages/identity/ng-package.json" "project": "packages/identity/ng-package.json"
}, },
@ -476,9 +406,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/identity"],
"{workspaceRoot}/coverage/packages/identity"
],
"options": { "options": {
"jestConfig": "packages/identity/jest.config.ts", "jestConfig": "packages/identity/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -487,22 +415,54 @@
"lint": { "lint": {
"builder": "@nrwl/linter:eslint", "builder": "@nrwl/linter:eslint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["packages/identity/src/**/*.ts", "packages/identity/src/**/*.html"]
"packages/identity/src/**/*.ts", },
"packages/identity/src/**/*.html" "outputs": ["{options.outputFile}"]
] }
},
"tags": [],
"implicitDependencies": ["core", "theme-shared", "permission-management"]
},
"oauth": {
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"root": "packages/oauth",
"sourceRoot": "packages/oauth/src",
"prefix": "abp",
"architect": {
"build": {
"builder": "@nrwl/angular:package",
"outputs": ["{workspaceRoot}/dist/packages/oauth"],
"options": {
"project": "packages/oauth/ng-package.json"
}, },
"outputs": [ "configurations": {
"{options.outputFile}" "production": {
] "tsConfig": "packages/oauth/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "packages/oauth/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "packages/oauth/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": ["packages/oauth/**/*.ts", "packages/oauth/**/*.html"]
}
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core"]
"core",
"theme-shared",
"permission-management"
]
}, },
"permission-management": { "permission-management": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -513,9 +473,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/permission-management"],
"{workspaceRoot}/dist/packages/permission-management"
],
"options": { "options": {
"project": "packages/permission-management/ng-package.json" "project": "packages/permission-management/ng-package.json"
}, },
@ -531,9 +489,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/permission-management"],
"{workspaceRoot}/coverage/packages/permission-management"
],
"options": { "options": {
"jestConfig": "packages/permission-management/jest.config.ts", "jestConfig": "packages/permission-management/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -547,16 +503,11 @@
"packages/permission-management/src/**/*.html" "packages/permission-management/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared"]
"core",
"theme-shared"
]
}, },
"schematics": { "schematics": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -567,9 +518,7 @@
"architect": { "architect": {
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/schematics"],
"{workspaceRoot}/coverage/packages/schematics"
],
"options": { "options": {
"jestConfig": "packages/schematics/jest.config.ts", "jestConfig": "packages/schematics/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -583,9 +532,7 @@
"packages/schematics/src/**/*.html" "packages/schematics/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [] "tags": []
@ -599,9 +546,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/setting-management"],
"{workspaceRoot}/dist/packages/setting-management"
],
"options": { "options": {
"project": "packages/setting-management/ng-package.json" "project": "packages/setting-management/ng-package.json"
}, },
@ -617,9 +562,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/setting-management"],
"{workspaceRoot}/coverage/packages/setting-management"
],
"options": { "options": {
"jestConfig": "packages/setting-management/jest.config.ts", "jestConfig": "packages/setting-management/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -633,17 +576,11 @@
"packages/setting-management/src/**/*.html" "packages/setting-management/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared", "components"]
"core",
"theme-shared",
"components"
]
}, },
"tenant-management": { "tenant-management": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -654,9 +591,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/tenant-management"],
"{workspaceRoot}/dist/packages/tenant-management"
],
"options": { "options": {
"project": "packages/tenant-management/ng-package.json" "project": "packages/tenant-management/ng-package.json"
}, },
@ -672,9 +607,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/tenant-management"],
"{workspaceRoot}/coverage/packages/tenant-management"
],
"options": { "options": {
"jestConfig": "packages/tenant-management/jest.config.ts", "jestConfig": "packages/tenant-management/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -688,17 +621,11 @@
"packages/tenant-management/src/**/*.html" "packages/tenant-management/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared", "feature-management"]
"core",
"theme-shared",
"feature-management"
]
}, },
"theme-basic": { "theme-basic": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -709,9 +636,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/theme-basic"],
"{workspaceRoot}/dist/packages/theme-basic"
],
"options": { "options": {
"project": "packages/theme-basic/ng-package.json" "project": "packages/theme-basic/ng-package.json"
}, },
@ -727,9 +652,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/theme-basic"],
"{workspaceRoot}/coverage/packages/theme-basic"
],
"options": { "options": {
"jestConfig": "packages/theme-basic/jest.config.ts", "jestConfig": "packages/theme-basic/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -743,17 +666,11 @@
"packages/theme-basic/src/**/*.html" "packages/theme-basic/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core", "theme-shared", "account-core"]
"core",
"theme-shared",
"account-core"
]
}, },
"theme-shared": { "theme-shared": {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
@ -764,9 +681,7 @@
"architect": { "architect": {
"build": { "build": {
"builder": "@nrwl/angular:package", "builder": "@nrwl/angular:package",
"outputs": [ "outputs": ["{workspaceRoot}/dist/packages/theme-shared"],
"{workspaceRoot}/dist/packages/theme-shared"
],
"options": { "options": {
"project": "packages/theme-shared/ng-package.json" "project": "packages/theme-shared/ng-package.json"
}, },
@ -782,9 +697,7 @@
}, },
"test": { "test": {
"builder": "@nrwl/jest:jest", "builder": "@nrwl/jest:jest",
"outputs": [ "outputs": ["{workspaceRoot}/coverage/packages/theme-shared"],
"{workspaceRoot}/coverage/packages/theme-shared"
],
"options": { "options": {
"jestConfig": "packages/theme-shared/jest.config.ts", "jestConfig": "packages/theme-shared/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
@ -798,15 +711,11 @@
"packages/theme-shared/src/**/*.html" "packages/theme-shared/src/**/*.html"
] ]
}, },
"outputs": [ "outputs": ["{options.outputFile}"]
"{options.outputFile}"
]
} }
}, },
"tags": [], "tags": [],
"implicitDependencies": [ "implicitDependencies": ["core","oauth"]
"core"
]
} }
} }
} }

@ -14,6 +14,7 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
import { FeatureManagementModule } from '@abp/ng.feature-management'; import { FeatureManagementModule } from '@abp/ng.feature-management';
// import { AbpOAuthModule } from '@abp/ng.oauth';
@NgModule({ @NgModule({
imports: [ imports: [
@ -26,6 +27,7 @@ import { FeatureManagementModule } from '@abp/ng.feature-management';
sendNullsAsQueryParam: false, sendNullsAsQueryParam: false,
skipGetAppConfiguration: false, skipGetAppConfiguration: false,
}), }),
// AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot(), ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(), AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(), IdentityConfigModule.forRoot(),

@ -8,10 +8,10 @@ import { OAuthService } from 'angular-oauth2-oidc';
}) })
export class HomeComponent { export class HomeComponent {
get hasLoggedIn(): boolean { get hasLoggedIn(): boolean {
return this.oAuthService.hasValidAccessToken(); return this.authService.isAuthenticated;
} }
constructor(private oAuthService: OAuthService, private authService: AuthService) {} constructor(private authService: AuthService) {}
login() { login() {
this.authService.navigateToLogin(); this.authService.navigateToLogin();

@ -86,8 +86,8 @@
"@popperjs/core": "~2.11.2", "@popperjs/core": "~2.11.2",
"@schematics/angular": "~15.0.1", "@schematics/angular": "~15.0.1",
"@swimlane/ngx-datatable": "^20.0.0", "@swimlane/ngx-datatable": "^20.0.0",
"@types/jest": "28.1.8", "@types/jest": "28.1.1",
"@types/node": "14.14.33", "@types/node": "16.11.7",
"@typescript-eslint/eslint-plugin": "5.44.0", "@typescript-eslint/eslint-plugin": "5.44.0",
"@typescript-eslint/parser": "5.44.0", "@typescript-eslint/parser": "5.44.0",
"angular-oauth2-oidc": "^15.0.1", "angular-oauth2-oidc": "^15.0.1",
@ -99,7 +99,7 @@
"eslint-config-prettier": "8.1.0", "eslint-config-prettier": "8.1.0",
"eslint-plugin-cypress": "^2.10.3", "eslint-plugin-cypress": "^2.10.3",
"got": "^11.5.2", "got": "^11.5.2",
"jest": "28.1.3", "jest": "28.1.1",
"jest-canvas-mock": "^2.3.1", "jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom": "28.1.1", "jest-environment-jsdom": "28.1.1",
"jest-preset-angular": "12.2.2", "jest-preset-angular": "12.2.2",
@ -119,18 +119,18 @@
"protractor": "~7.0.0", "protractor": "~7.0.0",
"rxjs": "7.5.6", "rxjs": "7.5.6",
"should-quote": "^1.0.0", "should-quote": "^1.0.0",
"ts-jest": "28.0.8", "ts-jest": "28.0.5",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"ts-toolbelt": "6.15.4", "ts-toolbelt": "6.15.4",
"tsickle": "^0.39.1", "tsickle": "^0.39.1",
"tslib": "^2.0.0", "tslib": "^2.3.0",
"tslint": "~6.1.0", "tslint": "~6.1.0",
"typescript": "4.8.4", "typescript": "4.8.4",
"zone.js": "0.11.4" "zone.js": "0.11.4"
}, },
"dependencies": {}, "dependencies": {},
"lint-staged": { "lint-staged": {
"**/*.{js,jsx,ts,tsx}":[ "**/*.{js,jsx,ts,tsx}": [
"npx prettier --write --config .prettierrc " "npx prettier --write --config .prettierrc "
] ]
} }

@ -0,0 +1,13 @@
import { CanActivate, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements IAuthGuard {
canActivate(): Observable<boolean> | boolean | UrlTree {
console.error('You should add @abp/ng-oauth packages or create your own auth packages.');
return false;
}
}
export interface IAuthGuard extends CanActivate {}

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { Params } from '@angular/router';
import { Observable, of } from 'rxjs';
import { LoginParams } from '../models/auth';
/**
* Abstract service for Authentication.
*/
@Injectable({
providedIn: 'root',
})
export class AuthService implements IAuthService {
constructor() {}
private warningMessage() {
console.error('You should add @abp/ng-oauth packages or create your own auth packages.');
}
init(): Promise<any> {
this.warningMessage();
return Promise.resolve(undefined);
}
login(params: LoginParams): Observable<any> {
this.warningMessage();
return of(undefined);
}
logout(queryParams?: Params): Observable<any> {
this.warningMessage();
return of(undefined);
}
navigateToLogin(queryParams?: Params): void {}
get isInternalAuth() {
throw new Error('not implemented');
return false;
}
get isAuthenticated(): boolean {
this.warningMessage();
return false;
}
}
export interface IAuthService {
get isInternalAuth(): boolean;
get isAuthenticated(): boolean;
init(): Promise<any>;
logout(queryParams?: Params): Observable<any>;
navigateToLogin(queryParams?: Params): void;
login(params: LoginParams): Observable<any>;
}

@ -1 +1,3 @@
export * from './ng-model.component'; export * from './ng-model.component';
export * from './auth.guard';
export * from './auth.service';

@ -1,9 +1,8 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core'; import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { OAuthModule, OAuthService, 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 { ReplaceableRouteContainerComponent } from './components/replaceable-route-container.component'; import { ReplaceableRouteContainerComponent } from './components/replaceable-route-container.component';
@ -16,9 +15,7 @@ import { InitDirective } from './directives/init.directive';
import { PermissionDirective } from './directives/permission.directive'; import { PermissionDirective } from './directives/permission.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive'; import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
import { StopPropagationDirective } from './directives/stop-propagation.directive'; import { StopPropagationDirective } from './directives/stop-propagation.directive';
import { OAuthConfigurationHandler } from './handlers/oauth-configuration.handler';
import { RoutesHandler } from './handlers/routes.handler'; import { RoutesHandler } from './handlers/routes.handler';
import { ApiInterceptor } from './interceptors/api.interceptor';
import { LocalizationModule } from './localization.module'; import { LocalizationModule } from './localization.module';
import { ABP } from './models/common'; import { ABP } from './models/common';
import { LocalizationPipe } from './pipes/localization.pipe'; import { LocalizationPipe } from './pipes/localization.pipe';
@ -27,7 +24,6 @@ import { ToInjectorPipe } from './pipes/to-injector.pipe';
import { CookieLanguageProvider } from './providers/cookie-language.provider'; import { CookieLanguageProvider } from './providers/cookie-language.provider';
import { LocaleProvider } from './providers/locale.provider'; import { LocaleProvider } from './providers/locale.provider';
import { LocalizationService } from './services/localization.service'; import { LocalizationService } from './services/localization.service';
import { oAuthStorage } from './strategies/auth-flow.strategy';
import { localizationContributor, LOCALIZATIONS } from './tokens/localization.token'; import { localizationContributor, LOCALIZATIONS } from './tokens/localization.token';
import { CORE_OPTIONS, coreOptionsFactory } from './tokens/options.token'; import { CORE_OPTIONS, coreOptionsFactory } from './tokens/options.token';
import { TENANT_KEY } from './tokens/tenant-key.token'; import { TENANT_KEY } from './tokens/tenant-key.token';
@ -37,12 +33,8 @@ import { getInitialData, localeInitializer } from './utils/initial-utils';
import { ShortDateTimePipe } from './pipes/short-date-time.pipe'; import { ShortDateTimePipe } from './pipes/short-date-time.pipe';
import { ShortTimePipe } from './pipes/short-time.pipe'; import { ShortTimePipe } from './pipes/short-time.pipe';
import { ShortDatePipe } from './pipes/short-date.pipe'; import { ShortDatePipe } from './pipes/short-date.pipe';
import { TimeoutLimitedOAuthService } from './services/timeout-limited-oauth.service';
import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider'; import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider';
import { AuthGuard } from './abstracts/auth.guard';
export function storageFactory(): OAuthStorage {
return oAuthStorage;
}
/** /**
* BaseCoreModule is the module that holds * BaseCoreModule is the module that holds
@ -77,7 +69,6 @@ export function storageFactory(): OAuthStorage {
ShortDatePipe, ShortDatePipe,
], ],
imports: [ imports: [
OAuthModule,
CommonModule, CommonModule,
HttpClientModule, HttpClientModule,
FormsModule, FormsModule,
@ -117,7 +108,6 @@ export class BaseCoreModule {}
imports: [ imports: [
BaseCoreModule, BaseCoreModule,
LocalizationModule, LocalizationModule,
OAuthModule,
HttpClientXsrfModule.withOptions({ HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN', cookieName: 'XSRF-TOKEN',
headerName: 'RequestVerificationToken', headerName: 'RequestVerificationToken',
@ -138,7 +128,6 @@ export class CoreModule {
return { return {
ngModule: RootCoreModule, ngModule: RootCoreModule,
providers: [ providers: [
OAuthModule.forRoot().providers,
LocaleProvider, LocaleProvider,
CookieLanguageProvider, CookieLanguageProvider,
{ {
@ -150,17 +139,6 @@ export class CoreModule {
useFactory: coreOptionsFactory, useFactory: coreOptionsFactory,
deps: ['CORE_OPTIONS'], deps: ['CORE_OPTIONS'],
}, },
{
provide: HTTP_INTERCEPTORS,
useExisting: ApiInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
multi: true,
deps: [OAuthConfigurationHandler],
useFactory: noop,
},
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
multi: true, multi: true,
@ -185,8 +163,7 @@ export class CoreModule {
deps: [RoutesHandler], deps: [RoutesHandler],
useFactory: noop, useFactory: noop,
}, },
{ provide: OAuthStorage, useFactory: storageFactory },
{ provide: OAuthService, useClass: TimeoutLimitedOAuthService },
{ provide: TENANT_KEY, useValue: options.tenantKey || '__tenant' }, { provide: TENANT_KEY, useValue: options.tenantKey || '__tenant' },
{ {
provide: LOCALIZATIONS, provide: LOCALIZATIONS,
@ -194,7 +171,7 @@ export class CoreModule {
useValue: localizationContributor(options.localizations), useValue: localizationContributor(options.localizations),
deps: [LocalizationService], deps: [LocalizationService],
}, },
IncludeLocalizationResourcesProvider IncludeLocalizationResourcesProvider,
], ],
}; };
} }

@ -1,2 +1 @@
export * from './auth.guard';
export * from './permission.guard'; export * from './permission.guard';

@ -1,2 +1 @@
export * from './oauth-configuration.handler';
export * from './routes.handler'; export * from './routes.handler';

@ -1,53 +1,24 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { finalize } from 'rxjs/operators'; import { finalize } from 'rxjs/operators';
import { SessionStateService } from '../services/session-state.service'; import { HttpWaitService } from '../services';
import { HttpWaitService } from '../services/http-wait.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ApiInterceptor implements HttpInterceptor { export class ApiInterceptor implements IApiInterceptor {
constructor( constructor(private httpWaitService: HttpWaitService) {}
private oAuthService: OAuthService,
private sessionState: SessionStateService, getAdditionalHeaders(existingHeaders?: HttpHeaders) {
private httpWaitService: HttpWaitService, return existingHeaders;
@Inject(TENANT_KEY) private tenantKey: string, }
) {}
intercept(request: HttpRequest<any>, next: HttpHandler) { intercept(request: HttpRequest<any>, next: HttpHandler) {
this.httpWaitService.addRequest(request); this.httpWaitService.addRequest(request);
return next return next.handle(request).pipe(finalize(() => this.httpWaitService.deleteRequest(request)));
.handle(
request.clone({
setHeaders: this.getAdditionalHeaders(request.headers),
}),
)
.pipe(finalize(() => this.httpWaitService.deleteRequest(request)));
} }
}
getAdditionalHeaders(existingHeaders?: HttpHeaders) { export interface IApiInterceptor extends HttpInterceptor {
const headers = {} as any; getAdditionalHeaders(existingHeaders?: HttpHeaders): HttpHeaders;
const token = this.oAuthService.getAccessToken();
if (!existingHeaders?.has('Authorization') && token) {
headers['Authorization'] = `Bearer ${token}`;
}
const lang = this.sessionState.getLanguage();
if (!existingHeaders?.has('Accept-Language') && lang) {
headers['Accept-Language'] = lang;
}
const tenant = this.sessionState.getTenant();
if (!existingHeaders?.has(this.tenantKey) && tenant?.id) {
headers[this.tenantKey] = tenant.id;
}
headers['X-Requested-With'] = 'XMLHttpRequest';
return headers;
}
} }

@ -1,6 +1,17 @@
import { UnaryFunction } from 'rxjs';
import { Injector } from '@angular/core';
export interface LoginParams { export interface LoginParams {
username: string; username: string;
password: string; password: string;
rememberMe?: boolean; rememberMe?: boolean;
redirectUrl?: string; redirectUrl?: string;
} }
export type PipeToLoginFn = (
params: Pick<LoginParams, 'redirectUrl' | 'rememberMe'>,
injector: Injector,
) => UnaryFunction<any, any>;
export type SetTokenResponseToStorageFn<T = any> = (injector: Injector, tokenRes: T) => void;
export type CheckAuthenticationStateFn = (injector: Injector) => void;

@ -7,10 +7,10 @@ export interface Environment {
hmr?: boolean; hmr?: boolean;
test?: boolean; test?: boolean;
localization?: { defaultResourceName?: string }; localization?: { defaultResourceName?: string };
oAuthConfig: AuthConfig; oAuthConfig?: AuthConfig;
production: boolean; production: boolean;
remoteEnv?: RemoteEnv; remoteEnv?: RemoteEnv;
[key:string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
export interface ApplicationInfo { export interface ApplicationInfo {
name: string; name: string;

@ -1,4 +1,3 @@
export * from './auth';
export * from './common'; export * from './common';
export * from './dtos'; export * from './dtos';
export * from './environment'; export * from './environment';
@ -7,3 +6,4 @@ export * from './replaceable-components';
export * from './rest'; export * from './rest';
export * from './session'; export * from './session';
export * from './utility'; export * from './utility';
export * from './auth';

@ -1,52 +0,0 @@
import { Injectable, Injector } from '@angular/core';
import { Params } from '@angular/router';
import { from, Observable } from 'rxjs';
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import { LoginParams } from '../models/auth';
import { AuthFlowStrategy, AUTH_FLOW_STRATEGY } from '../strategies/auth-flow.strategy';
import { EnvironmentService } from './environment.service';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private strategy: AuthFlowStrategy;
get isInternalAuth() {
return this.strategy.isInternalAuth;
}
constructor(protected injector: Injector) {}
async 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(queryParams?: Params): Observable<any> {
return this.strategy.logout(queryParams);
}
navigateToLogin(queryParams?: Params) {
this.strategy.navigateToLogin(queryParams);
}
login(params: LoginParams) {
return this.strategy.login(params);
}
}

@ -1,4 +1,3 @@
export * from './auth.service';
export * from './config-state.service'; export * from './config-state.service';
export * from './content-projection.service'; export * from './content-projection.service';
export * from './dom-insertion.service'; export * from './dom-insertion.service';
@ -19,4 +18,3 @@ export * from './routes.service';
export * from './session-state.service'; export * from './session-state.service';
export * from './subscription.service'; export * from './subscription.service';
export * from './track-by.service'; export * from './track-by.service';
export * from './timeout-limited-oauth.service';

@ -1,11 +0,0 @@
import { Injectable, NgZone, Optional } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable()
export class TimeoutLimitedOAuthService extends OAuthService {
protected override calcTimeout(storedAt: number, expiration: number): number {
const result = super.calcTimeout(storedAt, expiration);
const MAX_TIMEOUT_DURATION = 2147483647;
return result < MAX_TIMEOUT_DURATION ? result : MAX_TIMEOUT_DURATION - 1;
}
}

@ -1,262 +0,0 @@
import { HttpHeaders } from '@angular/common/http';
import { Injector } from '@angular/core';
import { Params, Router } from '@angular/router';
import {
AuthConfig,
OAuthErrorEvent,
OAuthInfoEvent,
OAuthService,
OAuthStorage
} from 'angular-oauth2-oidc';
import { from, Observable, of, pipe } from 'rxjs';
import { filter, switchMap, tap } from 'rxjs/operators';
import { LoginParams } from '../models/auth';
import { ConfigStateService } from '../services/config-state.service';
import { EnvironmentService } from '../services/environment.service';
import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { SessionStateService } from '../services/session-state.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
import { removeRememberMe, setRememberMe } from '../utils/auth-utils';
import { noop } from '../utils/common-utils';
export const oAuthStorage = localStorage;
export abstract class AuthFlowStrategy {
abstract readonly isInternalAuth: boolean;
protected httpErrorReporter: HttpErrorReporterService;
protected environment: EnvironmentService;
protected configState: ConfigStateService;
protected oAuthService: OAuthService;
protected oAuthConfig: AuthConfig;
protected sessionState: SessionStateService;
protected tenantKey: string;
abstract checkIfInternalAuth(queryParams?: Params): boolean;
abstract navigateToLogin(queryParams?: Params): void;
abstract logout(queryParams?: Params): Observable<any>;
abstract login(params?: LoginParams | Params): Observable<any>;
private catchError = err => {
this.httpErrorReporter.reportError(err);
return of(null);
};
constructor(protected injector: Injector) {
this.httpErrorReporter = injector.get(HttpErrorReporterService);
this.environment = injector.get(EnvironmentService);
this.configState = injector.get(ConfigStateService);
this.oAuthService = injector.get(OAuthService);
this.sessionState = injector.get(SessionStateService);
this.oAuthConfig = this.environment.getEnvironment().oAuthConfig;
this.tenantKey = injector.get(TENANT_KEY);
this.listenToOauthErrors();
}
async init(): Promise<any> {
const shouldClear = shouldStorageClear(
this.environment.getEnvironment().oAuthConfig.clientId,
oAuthStorage,
);
if (shouldClear) clearOAuthStorage(oAuthStorage);
this.oAuthService.configure(this.oAuthConfig);
this.oAuthService.events
.pipe(filter(event => event.type === 'token_refresh_error'))
.subscribe(() => this.navigateToLogin());
return this.oAuthService
.loadDiscoveryDocument()
.then(() => {
if (this.oAuthService.hasValidAccessToken() || !this.oAuthService.getRefreshToken()) {
return Promise.resolve();
}
return this.refreshToken();
})
.catch(this.catchError);
}
protected refreshToken() {
return this.oAuthService.refreshToken().catch(() => clearOAuthStorage());
}
protected listenToOauthErrors() {
this.oAuthService.events
.pipe(
filter(event => event instanceof OAuthErrorEvent),
tap(() => clearOAuthStorage()),
switchMap(() => this.configState.refreshAppState()),
)
.subscribe();
}
}
export class AuthCodeFlowStrategy extends AuthFlowStrategy {
readonly isInternalAuth = false;
async init() {
return super
.init()
.then(() => this.oAuthService.tryLogin().catch(noop))
.then(() => this.oAuthService.setupAutomaticSilentRefresh({}, 'access_token'));
}
navigateToLogin(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
}
checkIfInternalAuth(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
return false;
}
logout(queryParams?: Params) {
return from(this.oAuthService.revokeTokenAndLogout(this.getCultureParams(queryParams)));
}
login(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
return of(null);
}
private getCultureParams(queryParams?: Params) {
const lang = this.sessionState.getLanguage();
const culture = { culture: lang, 'ui-culture': lang };
return { ...(lang && culture), ...queryParams };
}
}
export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
readonly isInternalAuth = true;
private cookieKey = 'rememberMe';
private storageKey = 'passwordFlow';
private listenToTokenExpiration() {
this.oAuthService.events
.pipe(
filter(
event =>
event instanceof OAuthInfoEvent &&
event.type === 'token_expires' &&
event.info === 'access_token',
),
)
.subscribe(() => {
if (this.oAuthService.getRefreshToken()) {
this.refreshToken();
} else {
this.oAuthService.logOut();
removeRememberMe();
this.configState.refreshAppState().subscribe();
}
});
}
async init() {
if (!getCookieValueByName(this.cookieKey) && localStorage.getItem(this.storageKey)) {
this.oAuthService.logOut();
}
return super.init().then(() => this.listenToTokenExpiration());
}
navigateToLogin(queryParams?: Params) {
const router = this.injector.get(Router);
router.navigate(['/account/login'], { queryParams });
}
checkIfInternalAuth() {
return true;
}
login(params: LoginParams): Observable<any> {
const tenant = this.sessionState.getTenant();
return from(
this.oAuthService.fetchTokenUsingPasswordFlow(
params.username,
params.password,
new HttpHeaders({ ...(tenant && tenant.id && { [this.tenantKey]: tenant.id }) }),
),
).pipe(this.pipeToLogin(params));
}
pipeToLogin(params: Pick<LoginParams, 'redirectUrl' | 'rememberMe'>) {
const router = this.injector.get(Router);
return pipe(
switchMap(() => this.configState.refreshAppState()),
tap(() => {
setRememberMe(params.rememberMe);
if (params.redirectUrl) router.navigate([params.redirectUrl]);
}),
);
}
logout(queryParams?: Params) {
const router = this.injector.get(Router);
return from(this.oAuthService.revokeTokenAndLogout(queryParams)).pipe(
switchMap(() => this.configState.refreshAppState()),
tap(() => {
router.navigateByUrl('/');
removeRememberMe();
}),
);
}
protected refreshToken() {
return this.oAuthService.refreshToken().catch(() => {
clearOAuthStorage();
removeRememberMe();
});
}
}
export const AUTH_FLOW_STRATEGY = {
Code(injector: Injector) {
return new AuthCodeFlowStrategy(injector);
},
Password(injector: Injector) {
return new AuthPasswordFlowStrategy(injector);
},
};
export function clearOAuthStorage(storage: OAuthStorage = oAuthStorage) {
const keys = [
'access_token',
'id_token',
'refresh_token',
'nonce',
'PKCE_verifier',
'expires_at',
'id_token_claims_obj',
'id_token_expires_at',
'id_token_stored_at',
'access_token_stored_at',
'granted_scopes',
'session_state',
];
keys.forEach(key => storage.removeItem(key));
}
function shouldStorageClear(clientId: string, storage: OAuthStorage): boolean {
const key = 'abpOAuthClientId';
if (!storage.getItem(key)) {
storage.setItem(key, clientId);
return false;
}
const shouldClear = storage.getItem(key) !== clientId;
if (shouldClear) storage.setItem(key, clientId);
return shouldClear;
}
function getCookieValueByName(name: string) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : '';
}

@ -1,18 +1,17 @@
import { Component, Injector } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models';
import { SessionStateService } from '../services/session-state.service'; import { SessionStateService } from '../services/session-state.service';
import { EnvironmentService } from '../services/environment.service'; import { EnvironmentService } from '../services/environment.service';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../abstracts/auth.service';
import { ConfigStateService } from '../services/config-state.service'; import { ConfigStateService } from '../services/config-state.service';
import * as AuthFlowStrategy from '../strategies/auth-flow.strategy';
import { CORE_OPTIONS } from '../tokens/options.token'; import { CORE_OPTIONS } from '../tokens/options.token';
import { checkAccessToken, getInitialData, localeInitializer } from '../utils/initial-utils'; import { getInitialData, localeInitializer } from '../utils/initial-utils';
import * as environmentUtils from '../utils/environment-utils'; import * as environmentUtils from '../utils/environment-utils';
import * as multiTenancyUtils from '../utils/multi-tenancy-utils'; import * as multiTenancyUtils from '../utils/multi-tenancy-utils';
import { RestService } from '../services/rest.service';
const environment = { oAuthConfig: { issuer: 'test' } }; const environment = { oAuthConfig: { issuer: 'test' } };
@ -31,8 +30,8 @@ describe('InitialUtils', () => {
ConfigStateService, ConfigStateService,
AbpApplicationConfigurationService, AbpApplicationConfigurationService,
AuthService, AuthService,
OAuthService,
SessionStateService, SessionStateService,
RestService,
], ],
providers: [ providers: [
{ {
@ -52,6 +51,7 @@ describe('InitialUtils', () => {
const environmentService = spectator.inject(EnvironmentService); const environmentService = spectator.inject(EnvironmentService);
const configStateService = spectator.inject(ConfigStateService); const configStateService = spectator.inject(ConfigStateService);
const sessionStateService = spectator.inject(SessionStateService); const sessionStateService = spectator.inject(SessionStateService);
const parseTenantFromUrlSpy = jest.spyOn(multiTenancyUtils, 'parseTenantFromUrl'); const parseTenantFromUrlSpy = jest.spyOn(multiTenancyUtils, 'parseTenantFromUrl');
const getRemoteEnvSpy = jest.spyOn(environmentUtils, 'getRemoteEnv'); const getRemoteEnvSpy = jest.spyOn(environmentUtils, 'getRemoteEnv');
parseTenantFromUrlSpy.mockReturnValue(Promise.resolve()); parseTenantFromUrlSpy.mockReturnValue(Promise.resolve());
@ -82,20 +82,6 @@ describe('InitialUtils', () => {
}); });
}); });
describe('#checkAccessToken', () => {
test('should call logOut fn of OAuthService when token is valid and current user not found', async () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const clearOAuthStorageSpy = jest.spyOn(AuthFlowStrategy, 'clearOAuthStorage');
injectorSpy.mockReturnValueOnce({ getDeep: () => false });
injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => true });
checkAccessToken(injector);
expect(clearOAuthStorageSpy).toHaveBeenCalled();
});
});
describe('#localeInitializer', () => { describe('#localeInitializer', () => {
test('should resolve registerLocale', async () => { test('should resolve registerLocale', async () => {
const injector = spectator.inject(Injector); const injector = spectator.inject(Injector);

@ -0,0 +1,6 @@
import { InjectionToken } from '@angular/core';
import { CheckAuthenticationStateFn } from '../models/auth';
export const CHECK_AUTHENTICATION_STATE_FN_KEY = new InjectionToken<CheckAuthenticationStateFn>(
'CHECK_AUTHENTICATION_STATE_FN_KEY',
);

@ -7,3 +7,6 @@ export * from './manage-profile.token';
export * from './options.token'; export * from './options.token';
export * from './tenant-key.token'; export * from './tenant-key.token';
export * from './include-localization-resources.token'; export * from './include-localization-resources.token';
export * from './pipe-to-login.token';
export * from './set-token-response-to-storage.token';
export * from './check-authentication-state';

@ -1,21 +1,5 @@
import { InjectionToken, inject } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { EnvironmentService } from '../services/environment.service';
export const NAVIGATE_TO_MANAGE_PROFILE = new InjectionToken<() => void>( export const NAVIGATE_TO_MANAGE_PROFILE = new InjectionToken<() => void>(
'NAVIGATE_TO_MANAGE_PROFILE', 'NAVIGATE_TO_MANAGE_PROFILE',
{
providedIn: 'root',
factory: () => {
const environment = inject(EnvironmentService);
return () => {
window.open(
`${environment.getEnvironment().oAuthConfig.issuer}/Account/Manage?returnUrl=${
window.location.href
}`,
'_self',
);
};
},
},
); );

@ -0,0 +1,4 @@
import { InjectionToken } from '@angular/core';
import { PipeToLoginFn } from '../models/auth';
export const PIPE_TO_LOGIN_FN_KEY = new InjectionToken<PipeToLoginFn>('PIPE_TO_LOGIN_FN_KEY');

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

@ -1,5 +1,4 @@
export * from './array-utils'; export * from './array-utils';
export * from './auth-utils';
export * from './common-utils'; export * from './common-utils';
export * from './date-utils'; export * from './date-utils';
export * from './environment-utils'; export * from './environment-utils';

@ -1,20 +1,20 @@
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import { Injector } from '@angular/core'; import { InjectFlags, Injector } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { tap, catchError } from 'rxjs/operators'; import { tap, catchError } from 'rxjs/operators';
import { throwError } from 'rxjs'; import { lastValueFrom, throwError } from 'rxjs';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { Environment } from '../models/environment'; import { Environment } from '../models/environment';
import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models'; import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models';
import { AuthService } from '../services/auth.service';
import { ConfigStateService } from '../services/config-state.service'; import { ConfigStateService } from '../services/config-state.service';
import { EnvironmentService } from '../services/environment.service'; import { EnvironmentService } from '../services/environment.service';
import { SessionStateService } from '../services/session-state.service'; import { SessionStateService } from '../services/session-state.service';
import { clearOAuthStorage } from '../strategies/auth-flow.strategy';
import { CORE_OPTIONS } from '../tokens/options.token'; import { CORE_OPTIONS } from '../tokens/options.token';
import { APP_INIT_ERROR_HANDLERS } from '../tokens/app-config.token'; import { APP_INIT_ERROR_HANDLERS } from '../tokens/app-config.token';
import { getRemoteEnv } from './environment-utils'; import { getRemoteEnv } from './environment-utils';
import { parseTenantFromUrl } from './multi-tenancy-utils'; import { parseTenantFromUrl } from './multi-tenancy-utils';
import { AuthService } from '../abstracts';
import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state';
import { noop } from './common-utils';
export function getInitialData(injector: Injector) { export function getInitialData(injector: Injector) {
const fn = async () => { const fn = async () => {
@ -25,41 +25,36 @@ export function getInitialData(injector: Injector) {
environmentService.setState(options.environment as Environment); environmentService.setState(options.environment as Environment);
await getRemoteEnv(injector, options.environment); await getRemoteEnv(injector, options.environment);
await parseTenantFromUrl(injector); await parseTenantFromUrl(injector);
await injector.get(AuthService).init(); const authService = injector.get(AuthService, undefined, { optional: true });
const checkAuthenticationState = injector.get(CHECK_AUTHENTICATION_STATE_FN_KEY, noop, {
optional: true,
});
if (authService) {
await authService.init();
}
if (options.skipGetAppConfiguration) return; if (options.skipGetAppConfiguration) return;
return configState const result$ = configState.refreshAppState().pipe(
.refreshAppState() tap(() => checkAuthenticationState(injector)),
.pipe( tap(() => {
tap(() => checkAccessToken(injector)), const currentTenant = configState.getOne('currentTenant') as CurrentTenantDto;
tap(() => { injector.get(SessionStateService).setTenant(currentTenant);
const currentTenant = configState.getOne('currentTenant') as CurrentTenantDto; }),
injector.get(SessionStateService).setTenant(currentTenant); catchError(error => {
}), const appInitErrorHandlers = injector.get(APP_INIT_ERROR_HANDLERS, null);
catchError(error => { if (appInitErrorHandlers && appInitErrorHandlers.length) {
const appInitErrorHandlers = injector.get(APP_INIT_ERROR_HANDLERS, null); appInitErrorHandlers.forEach(func => func(error));
if (appInitErrorHandlers && appInitErrorHandlers.length) { }
appInitErrorHandlers.forEach(func => func(error));
}
return throwError(error); return throwError(error);
}), }),
) );
.toPromise(); await lastValueFrom(result$);
}; };
return fn; return fn;
} }
export function checkAccessToken(injector: Injector) {
const configState = injector.get(ConfigStateService);
const oAuth = injector.get(OAuthService);
if (oAuth.hasValidAccessToken() && !configState.getDeep('currentUser.id')) {
clearOAuthStorage();
}
}
export function localeInitializer(injector: Injector) { export function localeInitializer(injector: Injector) {
const fn = () => { const fn = () => {
const sessionState = injector.get(SessionStateService); const sessionState = injector.get(SessionStateService);

@ -6,7 +6,6 @@ export * from './lib/core.module';
export * from './lib/directives'; export * from './lib/directives';
export * from './lib/enums'; export * from './lib/enums';
export * from './lib/guards'; export * from './lib/guards';
export * from './lib/interceptors';
export * from './lib/localization.module'; export * from './lib/localization.module';
export * from './lib/models'; export * from './lib/models';
export * from './lib/pipes'; export * from './lib/pipes';
@ -23,3 +22,4 @@ export * from './lib/strategies';
export * from './lib/tokens'; export * from './lib/tokens';
export * from './lib/utils'; export * from './lib/utils';
export * from './lib/validators'; export * from './lib/validators';
export * from './lib/interceptors';

@ -0,0 +1,36 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "abp",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "abp",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
# oauth
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test oauth` to execute the unit tests.

@ -0,0 +1,22 @@
/* eslint-disable */
export default {
displayName: 'oauth',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/packages/oauth',
transform: {
'^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular',
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

@ -0,0 +1,14 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/packages/oauth",
"lib": {
"entryFile": "src/public-api.ts"
},
"allowedNonPeerDependencies": [
"@abp/utils",
"@abp/ng.core",
"angular-oauth2-oidc",
"just-clone",
"just-compare"
]
}

@ -0,0 +1,20 @@
{
"name": "@abp/ng.oauth",
"version": "7.0.0-rc.4",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/utils": "~7.0.0-rc.4",
"@abp/ng.core": "~7.0.0-rc.4",
"angular-oauth2-oidc": "^15.0.1",
"just-clone": "^6.1.1",
"just-compare": "^1.4.0",
"tslib": "^2.0.0"
},
"publishConfig": {
"access": "public"
}
}

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

@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
import { CanActivate, UrlTree } from '@angular/router'; import { CanActivate, UrlTree } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service'; import { AuthService, IAuthGuard } from '@abp/ng.core';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AbpOAuthGuard implements CanActivate, IAuthGuard {
constructor(private oauthService: OAuthService, private authService: AuthService) {} constructor(private oauthService: OAuthService, private authService: AuthService) {}
canActivate(): Observable<boolean> | boolean | UrlTree { canActivate(): Observable<boolean> | boolean | UrlTree {

@ -0,0 +1 @@
export * from './oauth-configuration.handler';

@ -2,9 +2,7 @@ import { Inject, Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import compare from 'just-compare'; import compare from 'just-compare';
import { filter, map } from 'rxjs/operators'; import { filter, map } from 'rxjs/operators';
import { ABP } from '../models/common'; import { ABP, EnvironmentService, CORE_OPTIONS } from '@abp/ng.core';
import { EnvironmentService } from '../services/environment.service';
import { CORE_OPTIONS } from '../tokens/options.token';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',

@ -0,0 +1,51 @@
import { HttpHandler, HttpHeaders, HttpRequest } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { finalize } from 'rxjs/operators';
import { SessionStateService, HttpWaitService, TENANT_KEY, IApiInterceptor } from '@abp/ng.core';
@Injectable({
providedIn: 'root',
})
export class OAuthApiInterceptor implements IApiInterceptor {
constructor(
private oAuthService: OAuthService,
private sessionState: SessionStateService,
private httpWaitService: HttpWaitService,
@Inject(TENANT_KEY) private tenantKey: string,
) {}
intercept(request: HttpRequest<any>, next: HttpHandler) {
this.httpWaitService.addRequest(request);
return next
.handle(
request.clone({
setHeaders: this.getAdditionalHeaders(request.headers),
}),
)
.pipe(finalize(() => this.httpWaitService.deleteRequest(request)));
}
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
const headers = {} as any;
const token = this.oAuthService.getAccessToken();
if (!existingHeaders?.has('Authorization') && token) {
headers['Authorization'] = `Bearer ${token}`;
}
const lang = this.sessionState.getLanguage();
if (!existingHeaders?.has('Accept-Language') && lang) {
headers['Accept-Language'] = lang;
}
const tenant = this.sessionState.getTenant();
if (!existingHeaders?.has(this.tenantKey) && tenant?.id) {
headers[this.tenantKey] = tenant.id;
}
headers['X-Requested-With'] = 'XMLHttpRequest';
return headers;
}
}

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

@ -0,0 +1,71 @@
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import {
ApiInterceptor,
AuthGuard,
AuthService,
CHECK_AUTHENTICATION_STATE_FN_KEY,
noop,
PIPE_TO_LOGIN_FN_KEY,
SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY,
} from '@abp/ng.core';
import { storageFactory } from './utils/storage.factory';
import { AbpOAuthService } from './services';
import { OAuthConfigurationHandler } from './handlers/oauth-configuration.handler';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { OAuthApiInterceptor } from './interceptors/api.interceptor';
import { AbpOAuthGuard } from './guards/oauth.guard';
import { NavigateToManageProfileProvider } from './providers';
import { checkAccessToken, pipeToLogin, setTokenResponseToStorage } from './utils';
@NgModule({
imports: [CommonModule, OAuthModule],
})
export class AbpOAuthModule {
static forRoot(): ModuleWithProviders<AbpOAuthModule> {
return {
ngModule: AbpOAuthModule,
providers: [
{
provide: AuthService,
useClass: AbpOAuthService,
},
{
provide: AuthGuard,
useClass: AbpOAuthGuard,
},
{
provide: ApiInterceptor,
useClass: OAuthApiInterceptor,
},
{
provide: PIPE_TO_LOGIN_FN_KEY,
useValue: pipeToLogin,
},
{
provide: SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY,
useValue: setTokenResponseToStorage,
},
{
provide: CHECK_AUTHENTICATION_STATE_FN_KEY,
useValue: checkAccessToken,
},
{
provide: HTTP_INTERCEPTORS,
useExisting: ApiInterceptor,
multi: true,
},
NavigateToManageProfileProvider,
{
provide: APP_INITIALIZER,
multi: true,
deps: [OAuthConfigurationHandler],
useFactory: noop,
},
OAuthModule.forRoot().providers,
{ provide: OAuthStorage, useFactory: storageFactory },
],
};
}
}

@ -0,0 +1 @@
export * from './navigate-to-manage-profile.provider';

@ -0,0 +1,21 @@
import { inject, Provider } from '@angular/core';
import { EnvironmentService, NAVIGATE_TO_MANAGE_PROFILE } from '@abp/ng.core';
export const NavigateToManageProfileProvider: Provider = {
provide: NAVIGATE_TO_MANAGE_PROFILE,
useFactory: () => {
const environment = inject(EnvironmentService);
return () => {
const env = environment.getEnvironment();
if (!env.oAuthConfig) {
console.warn('The oAuthConfig env is missing on environment.ts');
return;
}
window.open(
`${env.oAuthConfig.issuer}/Account/Manage?returnUrl=${window.location.href}`,
'_self',
);
};
},
};

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

@ -0,0 +1,57 @@
import { Injectable, Injector } from '@angular/core';
import { Params } from '@angular/router';
import { from, Observable, lastValueFrom } from 'rxjs';
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import { IAuthService, LoginParams } from '@abp/ng.core';
import { AuthFlowStrategy } from '../strategies';
import { EnvironmentService } from '@abp/ng.core';
import { AUTH_FLOW_STRATEGY } from '../tokens/auth-flow-strategy';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable({
providedIn: 'root',
})
export class AbpOAuthService implements IAuthService {
private strategy: AuthFlowStrategy;
get isInternalAuth() {
return this.strategy.isInternalAuth;
}
constructor(protected injector: Injector, private oAuthService: OAuthService) {}
async init() {
const environmentService = this.injector.get(EnvironmentService);
const result$ = 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),
);
return await lastValueFrom(result$);
}
logout(queryParams?: Params): Observable<any> {
return this.strategy.logout(queryParams);
}
navigateToLogin(queryParams?: Params) {
this.strategy.navigateToLogin(queryParams);
}
login(params: LoginParams) {
return this.strategy.login(params);
}
get isAuthenticated(): boolean {
return this.oAuthService.hasValidAccessToken();
}
}

@ -0,0 +1,39 @@
import { noop } from '@abp/ng.core';
import { Params } from '@angular/router';
import { from, of } from 'rxjs';
import { AuthFlowStrategy } from './auth-flow-strategy';
export class AuthCodeFlowStrategy extends AuthFlowStrategy {
readonly isInternalAuth = false;
async init() {
return super
.init()
.then(() => this.oAuthService.tryLogin().catch(noop))
.then(() => this.oAuthService.setupAutomaticSilentRefresh({}, 'access_token'));
}
navigateToLogin(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
}
checkIfInternalAuth(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
return false;
}
logout(queryParams?: Params) {
return from(this.oAuthService.revokeTokenAndLogout(this.getCultureParams(queryParams)));
}
login(queryParams?: Params) {
this.oAuthService.initCodeFlow('', this.getCultureParams(queryParams));
return of(null);
}
private getCultureParams(queryParams?: Params) {
const lang = this.sessionState.getLanguage();
const culture = { culture: lang, 'ui-culture': lang };
return { ...(lang && culture), ...queryParams };
}
}

@ -0,0 +1,108 @@
import { Injector } from '@angular/core';
import { Params } from '@angular/router';
import {
AuthConfig,
OAuthErrorEvent,
OAuthService as OAuthService2,
OAuthStorage,
} from 'angular-oauth2-oidc';
import { Observable, of } from 'rxjs';
import { filter, switchMap, tap } from 'rxjs/operators';
import {
LoginParams,
ConfigStateService,
EnvironmentService,
HttpErrorReporterService,
SessionStateService,
TENANT_KEY,
} from '@abp/ng.core';
import { clearOAuthStorage } from '../utils/clear-o-auth-storage';
import { oAuthStorage } from '../utils/oauth-storage';
export abstract class AuthFlowStrategy {
abstract readonly isInternalAuth: boolean;
protected httpErrorReporter: HttpErrorReporterService;
protected environment: EnvironmentService;
protected configState: ConfigStateService;
protected oAuthService: OAuthService2;
protected oAuthConfig: AuthConfig;
protected sessionState: SessionStateService;
protected tenantKey: string;
abstract checkIfInternalAuth(queryParams?: Params): boolean;
abstract navigateToLogin(queryParams?: Params): void;
abstract logout(queryParams?: Params): Observable<any>;
abstract login(params?: LoginParams | Params): Observable<any>;
private catchError = err => {
this.httpErrorReporter.reportError(err);
return of(null);
};
constructor(protected injector: Injector) {
this.httpErrorReporter = injector.get(HttpErrorReporterService);
this.environment = injector.get(EnvironmentService);
this.configState = injector.get(ConfigStateService);
this.oAuthService = injector.get(OAuthService2);
this.sessionState = injector.get(SessionStateService);
this.oAuthConfig = this.environment.getEnvironment().oAuthConfig;
this.tenantKey = injector.get(TENANT_KEY);
this.listenToOauthErrors();
}
async init(): Promise<any> {
const shouldClear = shouldStorageClear(
this.environment.getEnvironment().oAuthConfig.clientId,
oAuthStorage,
);
if (shouldClear) clearOAuthStorage(oAuthStorage);
this.oAuthService.configure(this.oAuthConfig);
this.oAuthService.events
.pipe(filter(event => event.type === 'token_refresh_error'))
.subscribe(() => this.navigateToLogin());
return this.oAuthService
.loadDiscoveryDocument()
.then(() => {
if (this.oAuthService.hasValidAccessToken() || !this.oAuthService.getRefreshToken()) {
return Promise.resolve();
}
return this.refreshToken();
})
.catch(this.catchError);
}
protected refreshToken() {
return this.oAuthService.refreshToken().catch(() => clearOAuthStorage());
}
protected listenToOauthErrors() {
this.oAuthService.events
.pipe(
filter(event => event instanceof OAuthErrorEvent),
tap(() => clearOAuthStorage()),
switchMap(() => this.configState.refreshAppState()),
)
.subscribe();
}
}
function shouldStorageClear(clientId: string, storage: OAuthStorage): boolean {
const key = 'abpOAuthClientId';
if (!storage.getItem(key)) {
storage.setItem(key, clientId);
return false;
}
const shouldClear = storage.getItem(key) !== clientId;
if (shouldClear) storage.setItem(key, clientId);
return shouldClear;
}

@ -0,0 +1,88 @@
import { filter, switchMap, tap } from 'rxjs/operators';
import { OAuthInfoEvent } from 'angular-oauth2-oidc';
import { Params, Router } from '@angular/router';
import { from, Observable, pipe } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { AuthFlowStrategy } from './auth-flow-strategy';
import { pipeToLogin, removeRememberMe, setRememberMe } from '../utils/auth-utils';
import { LoginParams } from '@abp/ng.core';
import { clearOAuthStorage } from '../utils/clear-o-auth-storage';
function getCookieValueByName(name: string) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : '';
}
export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
readonly isInternalAuth = true;
private cookieKey = 'rememberMe';
private storageKey = 'passwordFlow';
private listenToTokenExpiration() {
this.oAuthService.events
.pipe(
filter(
event =>
event instanceof OAuthInfoEvent &&
event.type === 'token_expires' &&
event.info === 'access_token',
),
)
.subscribe(() => {
if (this.oAuthService.getRefreshToken()) {
this.refreshToken();
} else {
this.oAuthService.logOut();
removeRememberMe();
this.configState.refreshAppState().subscribe();
}
});
}
async init() {
if (!getCookieValueByName(this.cookieKey) && localStorage.getItem(this.storageKey)) {
this.oAuthService.logOut();
}
return super.init().then(() => this.listenToTokenExpiration());
}
navigateToLogin(queryParams?: Params) {
const router = this.injector.get(Router);
return router.navigate(['/account/login'], { queryParams });
}
checkIfInternalAuth() {
return true;
}
login(params: LoginParams): Observable<any> {
const tenant = this.sessionState.getTenant();
return from(
this.oAuthService.fetchTokenUsingPasswordFlow(
params.username,
params.password,
new HttpHeaders({ ...(tenant && tenant.id && { [this.tenantKey]: tenant.id }) }),
),
).pipe(pipeToLogin(params, this.injector));
}
logout(queryParams?: Params) {
const router = this.injector.get(Router);
return from(this.oAuthService.revokeTokenAndLogout(queryParams)).pipe(
switchMap(() => this.configState.refreshAppState()),
tap(() => {
router.navigateByUrl('/');
removeRememberMe();
}),
);
}
protected refreshToken() {
return this.oAuthService.refreshToken().catch(() => {
clearOAuthStorage();
removeRememberMe();
});
}
}

@ -0,0 +1,3 @@
export * from './auth-flow-strategy';
export * from './auth-code-flow-strategy';
export * from './auth-password-flow-strategy';

@ -4,9 +4,7 @@ import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { Subject, timer } from 'rxjs'; import { Subject, timer } from 'rxjs';
import { ApiInterceptor } from '../interceptors/api.interceptor'; import { ApiInterceptor } from '../interceptors/api.interceptor';
import { HttpWaitService } from '../services/http-wait.service'; import { HttpWaitService, SessionStateService, TENANT_KEY } from '@abp/ng.core';
import { SessionStateService } from '../services/session-state.service';
import { TENANT_KEY } from '../tokens/tenant-key.token';
describe('ApiInterceptor', () => { describe('ApiInterceptor', () => {
let spectator: SpectatorService<ApiInterceptor>; let spectator: SpectatorService<ApiInterceptor>;

@ -1,13 +1,13 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { AuthGuard } from '../guards/auth.guard'; import { AbpOAuthGuard } from '../guards/oauth.guard';
import { AuthService } from '../services/auth.service'; import { AuthService } from '@Abp/ng.core';
describe('AuthGuard', () => { describe('AuthGuard', () => {
let spectator: SpectatorService<AuthGuard>; let spectator: SpectatorService<AbpOAuthGuard>;
let guard: AuthGuard; let guard: AbpOAuthGuard;
const createService = createServiceFactory({ const createService = createServiceFactory({
service: AuthGuard, service: AbpOAuthGuard,
mocks: [OAuthService, AuthService], mocks: [OAuthService, AuthService],
}); });

@ -0,0 +1,111 @@
import { Component, Injector } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { OAuthService } from 'angular-oauth2-oidc';
import {
CORE_OPTIONS,
EnvironmentService,
AuthService,
ConfigStateService,
AbpApplicationConfigurationService,
SessionStateService,
ApplicationConfigurationDto,
} from '@abp/ng.core';
import * as clearOAuthStorageDefault from '../utils/clear-o-auth-storage';
import { of } from 'rxjs';
import { checkAccessToken } from '../utils/check-access-token';
const environment = { oAuthConfig: { issuer: 'test' } };
@Component({
selector: 'abp-dummy',
template: '',
})
export class DummyComponent {}
describe('InitialUtils', () => {
let spectator: Spectator<DummyComponent>;
const createComponent = createComponentFactory({
component: DummyComponent,
mocks: [
EnvironmentService,
ConfigStateService,
AbpApplicationConfigurationService,
AuthService,
OAuthService,
SessionStateService,
],
providers: [
{
provide: CORE_OPTIONS,
useValue: {
environment,
registerLocaleFn: () => Promise.resolve(),
skipGetAppConfiguration: false,
},
},
],
});
beforeEach(() => (spectator = createComponent()));
describe('#getInitialData', () => {
let mockInjector;
let configStateService;
let authService;
beforeEach(() => {
mockInjector = {
get: spectator.inject,
};
configStateService = spectator.inject(ConfigStateService);
authService = spectator.inject(AuthService);
});
test('should called configStateService.refreshAppState', async () => {
const configRefreshAppStateSpy = jest.spyOn(configStateService, 'refreshAppState');
const appConfigRes = {
currentTenant: { id: 'test', name: 'testing' },
} as ApplicationConfigurationDto;
configRefreshAppStateSpy.mockReturnValue(of(appConfigRes));
// Todo: refactor it
// await initFactory(mockInjector)();
expect(configRefreshAppStateSpy).toHaveBeenCalled();
});
});
describe('#checkAccessToken', () => {
let injector;
let injectorSpy;
let clearOAuthStorageSpy;
beforeEach(() => {
injector = spectator.inject(Injector);
injectorSpy = jest.spyOn(injector, 'get');
clearOAuthStorageSpy = jest.spyOn(clearOAuthStorageDefault, 'clearOAuthStorage');
clearOAuthStorageSpy.mockReset();
});
test('should call logOut fn of OAuthService when token is valid and current user not found', async () => {
injectorSpy.mockReturnValueOnce({ getDeep: () => false });
injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => true });
checkAccessToken(injector);
expect(clearOAuthStorageSpy).toHaveBeenCalled();
});
test('should not call logOut fn of OAuthService when token is invalid', async () => {
injectorSpy.mockReturnValueOnce({ getDeep: () => true });
injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => false });
checkAccessToken(injector);
expect(clearOAuthStorageSpy).not.toHaveBeenCalled();
});
test('should not call logOut fn of OAuthService when token is valid but user is not found', async () => {
injectorSpy.mockReturnValueOnce({ getDeep: () => true });
injectorSpy.mockReturnValueOnce({ hasValidAccessToken: () => true });
checkAccessToken(injector);
expect(clearOAuthStorageSpy).not.toHaveBeenCalled();
});
});
});

@ -0,0 +1,5 @@
describe('Test', () => {
it('should be passed', () => {
expect(true).toBe(true);
});
});

@ -0,0 +1,12 @@
import { Injector } from '@angular/core';
import { AuthCodeFlowStrategy } from '../strategies/auth-code-flow-strategy';
import { AuthPasswordFlowStrategy } from '../strategies/auth-password-flow-strategy';
export const AUTH_FLOW_STRATEGY = {
Code(injector: Injector) {
return new AuthCodeFlowStrategy(injector);
},
Password(injector: Injector) {
return new AuthPasswordFlowStrategy(injector);
},
};

@ -0,0 +1 @@
export * from './auth-flow-strategy';

@ -3,13 +3,17 @@ import { Router } from '@angular/router';
import { OAuthStorage, TokenResponse } from 'angular-oauth2-oidc'; import { OAuthStorage, TokenResponse } from 'angular-oauth2-oidc';
import { pipe } from 'rxjs'; import { pipe } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators'; import { switchMap, tap } from 'rxjs/operators';
import { LoginParams } from '../models/auth'; import {
import { ConfigStateService } from '../services/config-state.service'; ConfigStateService,
LoginParams,
PipeToLoginFn,
SetTokenResponseToStorageFn,
} from '@abp/ng.core';
const cookieKey = 'rememberMe'; const cookieKey = 'rememberMe';
const storageKey = 'passwordFlow'; const storageKey = 'passwordFlow';
export function pipeToLogin( export const pipeToLogin: PipeToLoginFn = function (
params: Pick<LoginParams, 'redirectUrl' | 'rememberMe'>, params: Pick<LoginParams, 'redirectUrl' | 'rememberMe'>,
injector: Injector, injector: Injector,
) { ) {
@ -23,9 +27,12 @@ export function pipeToLogin(
if (params.redirectUrl) router.navigate([params.redirectUrl]); if (params.redirectUrl) router.navigate([params.redirectUrl]);
}), }),
); );
} };
export function setTokenResponseToStorage(injector: Injector, tokenRes: TokenResponse) { export const setTokenResponseToStorage: SetTokenResponseToStorageFn<TokenResponse> = function (
injector: Injector,
tokenRes: TokenResponse,
) {
const { access_token, refresh_token, scope: grantedScopes, expires_in } = tokenRes; const { access_token, refresh_token, scope: grantedScopes, expires_in } = tokenRes;
const storage = injector.get(OAuthStorage); const storage = injector.get(OAuthStorage);
@ -43,7 +50,7 @@ export function setTokenResponseToStorage(injector: Injector, tokenRes: TokenRes
const expiresAt = now.getTime() + expiresInMilliSeconds; const expiresAt = now.getTime() + expiresInMilliSeconds;
storage.setItem('expires_at', '' + expiresAt); storage.setItem('expires_at', '' + expiresAt);
} }
} };
export function setRememberMe(remember: boolean) { export function setRememberMe(remember: boolean) {
removeRememberMe(); removeRememberMe();

@ -0,0 +1,12 @@
import { Injector } from '@angular/core';
import { CheckAuthenticationStateFn, ConfigStateService } from '@abp/ng.core';
import { OAuthService } from 'angular-oauth2-oidc';
import { clearOAuthStorage } from './clear-o-auth-storage';
export const checkAccessToken: CheckAuthenticationStateFn = function (injector: Injector) {
const configState = injector.get(ConfigStateService);
const oAuth = injector.get(OAuthService);
if (oAuth.hasValidAccessToken() && !configState.getDeep('currentUser.id')) {
clearOAuthStorage();
}
};

@ -0,0 +1,21 @@
import { OAuthStorage } from 'angular-oauth2-oidc';
import { oAuthStorage } from './oauth-storage';
export function clearOAuthStorage(storage: OAuthStorage = oAuthStorage) {
const keys = [
'access_token',
'id_token',
'refresh_token',
'nonce',
'PKCE_verifier',
'expires_at',
'id_token_claims_obj',
'id_token_expires_at',
'id_token_stored_at',
'access_token_stored_at',
'granted_scopes',
'session_state',
];
keys.forEach(key => storage.removeItem(key));
}

@ -0,0 +1,5 @@
export * from './oauth-storage';
export * from './storage.factory';
export * from './auth-utils';
export * from './clear-o-auth-storage';
export * from './check-access-token';

@ -0,0 +1 @@
export const oAuthStorage = localStorage;

@ -0,0 +1,6 @@
import { OAuthStorage } from 'angular-oauth2-oidc';
import { oAuthStorage } from './oauth-storage';
export function storageFactory(): OAuthStorage {
return oAuthStorage;
}

@ -0,0 +1,9 @@
export * from './lib/oauth.module';
export * from './lib/utils';
export * from './lib/tokens';
export * from './lib/services';
export * from './lib/strategies';
export * from './lib/handlers';
export * from './lib/interceptors';
export * from './lib/guards';
export * from './lib/providers';

@ -0,0 +1 @@
import 'jest-preset-angular/setup-jest';

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"target": "es2020"
}
}

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"target": "ES2022",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"],
"useDefineForClassFields": false
},
"exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"],
"include": ["**/*.ts"]
}

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false,
"target": "ES2022",
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

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

@ -83,6 +83,7 @@ export class ErrorHandler {
protected cfRes: ComponentFactoryResolver; protected cfRes: ComponentFactoryResolver;
protected rendererFactory: RendererFactory2; protected rendererFactory: RendererFactory2;
protected httpErrorConfig: HttpErrorConfig; protected httpErrorConfig: HttpErrorConfig;
private authService: AuthService;
constructor(protected injector: Injector) { constructor(protected injector: Injector) {
this.httpErrorReporter = injector.get(HttpErrorReporterService); this.httpErrorReporter = injector.get(HttpErrorReporterService);
@ -91,6 +92,7 @@ export class ErrorHandler {
this.cfRes = injector.get(ComponentFactoryResolver); this.cfRes = injector.get(ComponentFactoryResolver);
this.rendererFactory = injector.get(RendererFactory2); this.rendererFactory = injector.get(RendererFactory2);
this.httpErrorConfig = injector.get('HTTP_ERROR_CONFIG'); this.httpErrorConfig = injector.get('HTTP_ERROR_CONFIG');
this.authService = this.injector.get(AuthService);
this.listenToRestError(); this.listenToRestError();
this.listenToRouterError(); this.listenToRouterError();
@ -284,7 +286,7 @@ export class ErrorHandler {
} }
private navigateToLogin() { private navigateToLogin() {
this.injector.get(AuthService).navigateToLogin(); this.authService.navigateToLogin();
} }
createErrorComponent(instance: Partial<HttpErrorWrapperComponent>) { createErrorComponent(instance: Partial<HttpErrorWrapperComponent>) {

@ -17,7 +17,8 @@ const packageMap = {
'tenant-management': 'ng.tenant-management', 'tenant-management': 'ng.tenant-management',
'theme-basic': 'ng.theme.basic', 'theme-basic': 'ng.theme.basic',
'theme-shared': 'ng.theme.shared', 'theme-shared': 'ng.theme.shared',
'schematics':'ng.schematics' 'schematics':'ng.schematics',
oauth:'ng.oauth'
}; };
program.option('-t, --templates <templates>', 'template dirs', false); program.option('-t, --templates <templates>', 'template dirs', false);
program.option('-p, --template-path <templatePath>', 'root template path', false); program.option('-p, --template-path <templatePath>', 'root template path', false);
@ -25,8 +26,9 @@ program.parse(process.argv);
const templates = program.templates ? program.templates.split(',') : defaultTemplates; const templates = program.templates ? program.templates.split(',') : defaultTemplates;
const templateRootPath = program.templatePath ? program.templatePath : defaultTemplatePath; const templateRootPath = program.templatePath ? program.templatePath : defaultTemplatePath;
(async () => { (async () => {
await execa('yarn', ['build'], { await execa('yarn', ['build:all'], {
stdout: 'inherit', stdout: 'inherit',
cwd:'../'
}); });
await installPackages(); await installPackages();

@ -43,7 +43,8 @@
"@abp/ng.theme.basic/testing": ["packages/theme-basic/testing/src/public-api.ts"], "@abp/ng.theme.basic/testing": ["packages/theme-basic/testing/src/public-api.ts"],
"@abp/ng.theme.shared": ["packages/theme-shared/src/public-api.ts"], "@abp/ng.theme.shared": ["packages/theme-shared/src/public-api.ts"],
"@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"], "@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"],
"@abp/ng.theme.shared/testing": ["packages/theme-shared/testing/src/public-api.ts"] "@abp/ng.theme.shared/testing": ["packages/theme-shared/testing/src/public-api.ts"],
"@abp/ng.oauth": ["packages/oauth/src/public-api.ts"]
} }
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]

@ -15,6 +15,7 @@
"@abp/ng.account": "~7.0.0-rc.5", "@abp/ng.account": "~7.0.0-rc.5",
"@abp/ng.components": "~7.0.0-rc.5", "@abp/ng.components": "~7.0.0-rc.5",
"@abp/ng.core": "~7.0.0-rc.5", "@abp/ng.core": "~7.0.0-rc.5",
"@abp/ng.oauth":"~7.0.0-rc.5",
"@abp/ng.identity": "~7.0.0-rc.5", "@abp/ng.identity": "~7.0.0-rc.5",
"@abp/ng.setting-management": "~7.0.0-rc.5", "@abp/ng.setting-management": "~7.0.0-rc.5",
"@abp/ng.tenant-management": "~7.0.0-rc.5", "@abp/ng.tenant-management": "~7.0.0-rc.5",

@ -15,6 +15,7 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
import { FeatureManagementModule } from '@abp/ng.feature-management'; import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth';
@NgModule({ @NgModule({
imports: [ imports: [
@ -25,6 +26,7 @@ import { FeatureManagementModule } from '@abp/ng.feature-management';
environment, environment,
registerLocaleFn: registerLocale(), registerLocaleFn: registerLocale(),
}), }),
AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot(), ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(), AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(), IdentityConfigModule.forRoot(),

@ -1,15 +0,0 @@
{
"recommendations": [
"angular.ng-template",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-tslint-plugin",
"visualstudioexptteam.vscodeintellicode",
"christian-kohler.path-intellisense",
"christian-kohler.npm-intellisense",
"Mikael.Angular-BeastCode",
"xabikos.JavaScriptSnippets",
"msjsdiag.debugger-for-chrome",
"donjayamanne.githistory",
"oderwat.indent-rainbow"
]
}

@ -15,6 +15,7 @@
"@abp/ng.account": "~7.0.0-rc.5", "@abp/ng.account": "~7.0.0-rc.5",
"@abp/ng.components": "~7.0.0-rc.5", "@abp/ng.components": "~7.0.0-rc.5",
"@abp/ng.core": "~7.0.0-rc.5", "@abp/ng.core": "~7.0.0-rc.5",
"@abp/ng.oauth":"~7.0.0-rc.5",
"@abp/ng.identity": "~7.0.0-rc.5", "@abp/ng.identity": "~7.0.0-rc.5",
"@abp/ng.setting-management": "~7.0.0-rc.5", "@abp/ng.setting-management": "~7.0.0-rc.5",
"@abp/ng.tenant-management": "~7.0.0-rc.5", "@abp/ng.tenant-management": "~7.0.0-rc.5",

@ -15,6 +15,7 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
import { FeatureManagementModule } from '@abp/ng.feature-management'; import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth';
@NgModule({ @NgModule({
imports: [ imports: [
@ -25,6 +26,7 @@ import { FeatureManagementModule } from '@abp/ng.feature-management';
environment, environment,
registerLocaleFn: registerLocale(), registerLocaleFn: registerLocale(),
}), }),
AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot(), ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(), AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(), IdentityConfigModule.forRoot(),

@ -18,6 +18,7 @@
"@abp/ng.account": "~7.0.0-rc.5", "@abp/ng.account": "~7.0.0-rc.5",
"@abp/ng.components": "~7.0.0-rc.5", "@abp/ng.components": "~7.0.0-rc.5",
"@abp/ng.core": "~7.0.0-rc.5", "@abp/ng.core": "~7.0.0-rc.5",
"@abp/ng.oauth":"~7.0.0-rc.5",
"@abp/ng.identity": "~7.0.0-rc.5", "@abp/ng.identity": "~7.0.0-rc.5",
"@abp/ng.setting-management": "~7.0.0-rc.5", "@abp/ng.setting-management": "~7.0.0-rc.5",
"@abp/ng.tenant-management": "~7.0.0-rc.5", "@abp/ng.tenant-management": "~7.0.0-rc.5",

@ -15,6 +15,7 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
import { FeatureManagementModule } from '@abp/ng.feature-management'; import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth';
@NgModule({ @NgModule({
imports: [ imports: [
@ -27,6 +28,7 @@ import { FeatureManagementModule } from '@abp/ng.feature-management';
sendNullsAsQueryParam: false, sendNullsAsQueryParam: false,
skipGetAppConfiguration: false, skipGetAppConfiguration: false,
}), }),
AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot(), ThemeSharedModule.forRoot(),
AccountConfigModule.forRoot(), AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(), IdentityConfigModule.forRoot(),

Loading…
Cancel
Save