diff --git a/docker-compose.yml b/docker-compose.yml index ff76bbb4d..ec8553398 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,3 +23,5 @@ services: - MIFOS_DEFAULT_LANGUAGE=en-US - MIFOS_SUPPORTED_LANGUAGES=cs-CS,de-DE,en-US,es-MX,fr-FR,it-IT,ko-KO,lt-LT,lv-LV,ne-NE,pt-PT,sw-SW - MIFOS_SESSION_IDLE_TIMEOUT=300000 + - MIFOS_OAUTH_SERVER_ENABLED=false + \ No newline at end of file diff --git a/env.sample b/env.sample index 25f19916b..9472e291e 100644 --- a/env.sample +++ b/env.sample @@ -20,3 +20,12 @@ MIFOS_SUPPORTED_LANGUAGES=cs-CS,de-DE,en-US,es-MX,fr-FR,it-IT,ko-KO,lt-LT,lv-LV, MIFOS_PRELOAD_CLIENTS=true MIFOS_DEFAULT_CHAR_DELIMITER=, + +#Set to true for enabling the Oauth +MIFOS_OAUTH_SERVER_ENABLED=false + +#Set the URL of the Oauth server, only used while Oauth is enabled +MIFOS_OAUTH_SERVER_URL=https://{$URL_OAUTH_SERVER} + +#Set the application client id, only used while Oauth is enabled +MIFOS_OAUTH_CLIENT_ID=web-app diff --git a/src/app/core/authentication/authentication.interceptor.ts b/src/app/core/authentication/authentication.interceptor.ts index 44c460754..2db6cf906 100644 --- a/src/app/core/authentication/authentication.interceptor.ts +++ b/src/app/core/authentication/authentication.interceptor.ts @@ -18,6 +18,7 @@ const httpOptions = { /** Authorization header. */ const authorizationHeader = 'Authorization'; +const authorizationTenantHeader = 'Fineract-Platform-TenantId'; /** Two factor access token header. */ const twoFactorAccessTokenHeader = 'Fineract-Platform-TFA-Token'; @@ -67,6 +68,14 @@ export class AuthenticationInterceptor implements HttpInterceptor { delete httpOptions.headers[authorizationHeader]; } + /** + * Removes the authorization header. + */ + removeAuthorizationTenant() { + delete httpOptions.headers[authorizationHeader]; + delete httpOptions.headers[authorizationTenantHeader]; + } + /** * Removes the two factor access token header. */ diff --git a/src/app/core/authentication/authentication.service.ts b/src/app/core/authentication/authentication.service.ts index 7eba09373..07913e093 100644 --- a/src/app/core/authentication/authentication.service.ts +++ b/src/app/core/authentication/authentication.service.ts @@ -1,6 +1,6 @@ /** Angular Imports */ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http'; /** rxjs Imports */ import { Observable, of } from 'rxjs'; @@ -94,11 +94,15 @@ export class AuthenticationService { if (environment.oauth.enabled) { let httpParams = new HttpParams(); - httpParams = httpParams.set('client_id', 'community-app'); + httpParams = httpParams.set('username', loginContext.username); + httpParams = httpParams.set('password', loginContext.password); + httpParams = httpParams.set('client_id', `${environment.oauth.appId}`); httpParams = httpParams.set('grant_type', 'password'); - return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token`, {}, { params: httpParams }) + let headers = new HttpHeaders(); + headers = headers.set('Content-Type', 'application/x-www-form-urlencoded') + return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/token`, httpParams.toString(), {headers: headers}) .pipe( - map((tokenResponse: OAuth2Token) => { + map((tokenResponse: OAuth2Token) => { this.getUserDetails(tokenResponse); return of(true); }) @@ -120,10 +124,11 @@ export class AuthenticationService { * Sets the oauth2 token refresh time. * @param {OAuth2Token} tokenResponse OAuth2 Token details. */ - private getUserDetails(tokenResponse: OAuth2Token) { - const httpParams = new HttpParams().set('access_token', tokenResponse.access_token); + private getUserDetails(tokenResponse: OAuth2Token) { this.refreshTokenOnExpiry(tokenResponse.expires_in); - this.http.get('/userdetails', { params: httpParams }) + let headers = new HttpHeaders(); + headers = headers.set('Authorization', 'bearer '+tokenResponse.access_token) + this.http.disableApiPrefix().get(`${environment.serverUrl}/userdetails`,{ headers: headers }) .subscribe((credentials: Credentials) => { this.onLoginSuccess(credentials); if (!credentials.shouldRenewPassword) { @@ -136,7 +141,7 @@ export class AuthenticationService { * Sets the oauth2 token to refresh on expiry. * @param {number} expiresInTime OAuth2 token expiry time in seconds. */ - private refreshTokenOnExpiry(expiresInTime: number) { + private refreshTokenOnExpiry(expiresInTime: number) { setTimeout(() => this.refreshOAuthAccessToken(), expiresInTime * 1000); } @@ -144,13 +149,21 @@ export class AuthenticationService { * Refreshes the oauth2 authorization token. */ private refreshOAuthAccessToken() { - const oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)).refresh_token; - this.authenticationInterceptor.removeAuthorization(); - let httpParams = new HttpParams(); - httpParams = httpParams.set('client_id', 'community-app'); - httpParams = httpParams.set('grant_type', 'refresh_token'); + var oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)); + if (oAuthRefreshToken == null) { + return; + } + oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)).refresh_token; + this.authenticationInterceptor.removeAuthorization(); + const credentials = JSON.parse(this.storage.getItem(this.credentialsStorageKey)); + let httpParams = new HttpParams(); + httpParams = httpParams.set('username', credentials.username); + httpParams = httpParams.set('client_id', `${environment.oauth.appId}`); httpParams = httpParams.set('refresh_token', oAuthRefreshToken); - this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/oauth/token`, {}, { params: httpParams }) + httpParams = httpParams.set('grant_type', 'refresh_token'); + let headers = new HttpHeaders(); + headers = headers.set('Content-Type', 'application/x-www-form-urlencoded') + return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/token`, httpParams.toString(), {headers: headers}) .subscribe((tokenResponse: OAuth2Token) => { this.storage.setItem(this.oAuthTokenDetailsStorageKey, JSON.stringify(tokenResponse)); this.authenticationInterceptor.setAuthorizationToken(tokenResponse.access_token); @@ -160,7 +173,7 @@ export class AuthenticationService { this.storage.setItem(this.credentialsStorageKey, JSON.stringify(credentials)); }); } - + /** * Sets the authorization token followed by one of the following: * @@ -193,6 +206,22 @@ export class AuthenticationService { } } + /** + * Logout ongoing Oauth2 session. + */ + private logoutAuthSession() { + const oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)).refresh_token; + const credentials = JSON.parse(this.storage.getItem(this.credentialsStorageKey)); + this.authenticationInterceptor.removeAuthorizationTenant(); + let httpParams = new HttpParams(); + httpParams = httpParams.set('username', credentials.username); + httpParams = httpParams.set('client_id', `${environment.oauth.appId}`); + httpParams = httpParams.set('refresh_token', oAuthRefreshToken); + let headers = new HttpHeaders(); + headers = headers.set('Content-Type', 'application/x-www-form-urlencoded') + return this.http.disableApiPrefix().post(`${environment.oauth.serverUrl}/logout`, httpParams.toString(), {headers: headers}).subscribe(); + } + /** * Logs out the authenticated user and clears the credentials from storage. * @returns {Observable} True if the user was logged out successfully. @@ -203,6 +232,10 @@ export class AuthenticationService { this.http.post('/twofactor/invalidate', { token: twoFactorToken.token }).subscribe(); this.authenticationInterceptor.removeTwoFactorAuthorization(); } + const oAuthRefreshToken = JSON.parse(this.storage.getItem(this.oAuthTokenDetailsStorageKey)); + if (oAuthRefreshToken) { + this.logoutAuthSession(); + } this.authenticationInterceptor.removeAuthorization(); this.setCredentials(); this.resetDialog(); diff --git a/src/assets/env.js b/src/assets/env.js index 0c7ce5984..334e1c240 100644 --- a/src/assets/env.js +++ b/src/assets/env.js @@ -35,4 +35,13 @@ // Time in milliseconds for Session idle timeout, default 300000 seconds window['env']['sessionIdleTimeout'] = '0'; + // OAuth Server Enabled + window['env']['oauthServerEnabled'] = ''; + + // OAuth Server URL + window['env']['oauthServerUrl'] = ''; + + // OAuth Client Id + window['env']['oauthAppId'] = ''; + })(this); diff --git a/src/assets/env.template.js b/src/assets/env.template.js index df44f8701..9694199b3 100644 --- a/src/assets/env.template.js +++ b/src/assets/env.template.js @@ -15,7 +15,7 @@ window['env']['defaultLanguage'] = '$MIFOS_DEFAULT_LANGUAGE'; window['env']['supportedLanguages'] = '$MIFOS_SUPPORTED_LANGUAGES'; - window['env']['preloadClients'] = '$MIFOS_PRELOAD_CLIENTS'; + window['env']['preloadClients'] = $MIFOS_PRELOAD_CLIENTS; // Char delimiter to Export CSV options: ',' ';' '|' ' ' window['env']['defaultCharDelimiter'] = '$MIFOS_DEFAULT_CHAR_DELIMITER'; @@ -34,4 +34,14 @@ // Time in milliseconds for Session idle timeout, default 300000 seconds window['env']['sessionIdleTimeout'] = '$MIFOS_SESSION_IDLE_TIMEOUT'; + + // OAuth Server Enabled + window['env']['oauthServerEnabled'] = $MIFOS_OAUTH_SERVER_ENABLED; + + // OAuth Server URL + window['env']['oauthServerUrl'] = '$MIFOS_OAUTH_SERVER_URL'; + + // OAuth Client Id + window['env']['oauthAppId'] = '$MIFOS_OAUTH_CLIENT_ID'; + })(this); diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index d524b38d6..b0cbec57c 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -13,13 +13,15 @@ export const environment = { 'https://dev.mifos.io,https://demo.mifos.io,https://qa.mifos.io,https://staging.mifos.io,https://mobile.mifos.io,https://demo.fineract.dev,https://localhost:8443', // For connecting to server running elsewhere set the base API URL baseApiUrl: window['env']['fineractApiUrl'] || window.location.protocol + '//' + window.location.hostname, + oauthServerUrl: window['env']['oauthServerUrl'] || window['env']['fineractApiUrl'] + window['env']['apiProvider'], allowServerSwitch: env.allow_switching_backend_instance, apiProvider: window['env']['apiProvider'] || '/fineract-provider/api', apiVersion: window['env']['apiVersion'] || '/v1', serverUrl: '', oauth: { - enabled: false, // For connecting to Mifos X using OAuth2 Authentication change the value to true - serverUrl: '' + enabled: window['env']['oauthServerEnabled'] || false, // For connecting to Mifos X using OAuth2 Authentication change the value to true + serverUrl: window['env']['oauthServerUrl'] || '', + appId: window['env']['oauthAppId'] || '' }, warningDialog: { title: 'Warning', @@ -47,4 +49,4 @@ export const environment = { // Server URL environment.serverUrl = `${environment.baseApiUrl}${environment.apiProvider}${environment.apiVersion}`; -environment.oauth.serverUrl = `${environment.baseApiUrl}${environment.apiProvider}`; + diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 0349af258..0ba4f6c96 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -24,8 +24,9 @@ export const environment = { apiVersion: window['env']['apiVersion'] || '/v1', serverUrl: '', oauth: { - enabled: false, // For connecting to Mifos X using OAuth2 Authentication change the value to true - serverUrl: '' + enabled: window['env']['oauthServerEnabled'] || false, // For connecting to Mifos X using OAuth2 Authentication change the value to true + serverUrl: window['env']['oauthServerUrl'] || '', + appId: window['env']['oauthAppId'] || '' }, warningDialog: { title: 'Warning', @@ -53,4 +54,4 @@ export const environment = { // Server URL environment.serverUrl = `${environment.baseApiUrl}${environment.apiProvider}${environment.apiVersion}`; -environment.oauth.serverUrl = `${environment.baseApiUrl}${environment.apiProvider}`; +