import { Injectable } from '@angular/core'; import { Request } from '../models/request'; import { TranslatorService } from '../translator/translator.service'; import { ConnectorService } from '../connector/connector.service'; import { Observable, throwError } from 'rxjs'; import { CheckPatientRegisterationRequest } from './models/check-patient-registeration.request'; import { LoginRequest } from './models/login.request'; import { Response } from '../models/response'; import { AuthenticatedUser } from './models/authenticated-user'; import { CommonService } from '../common/common.service'; import { CheckUserAuthenticationRequest } from './models/check-user-auth.request'; import { CheckActivationCodeRequest } from './models/check-activation-code.request'; import { CheckUserAuthenticationResponse } from './models/check-user-auth.response'; import { CheckActivationCodeResponse } from './models/check-activation-code.response'; import { NativeStorage } from '@ionic-native/native-storage/ngx'; import { CheckRegisterationCodeRequest } from './models/check-registeration-code.request'; import { RegisterInformationRequest } from './models/register-information.request'; import { ForgotFileIDResponse } from './models/forgot-File-ID.response'; import { EmailRequest } from '../models/email-request'; import { EmailInput } from '../../ui/email/models/email-input'; import { UserLocalNotificationService } from '../user-local-notification/user-local-notification.service'; import { GetLoginInfoRequest } from './models/get-login-info.request'; import { GetLoginInfoResponse } from './models/get-login-info.response'; import { analyzeAndValidateNgModules } from '@angular/compiler'; import { Events } from '@ionic/angular'; import { InternationalMobileComponent } from '../../ui/mobile-number/international-mobile/international-mobile.component'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { public static MOBILE_USER = 102; /* login methods */ public static loginURL = 'Services/Authentication.svc/REST/CheckPatientAuthentication'; public static checkUserAuthURL = 'Services/Authentication.svc/REST/CheckPatientAuthentication'; public static login = 'Services/ERP.svc/REST/MemberLogin'; public static activationCodeURL = 'Services/Authentication.svc/REST/CheckActivationCode'; public static getLoginInfoURL = 'Services/Authentication.svc/REST/GetMobileLoginInfo'; /* register methods */ public static checkPatientForRegisterationURL = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; public static sendSmsForRegisterURL = 'Services/Authentication.svc/REST/SendSMSForPatientRegisteration'; public static registerTempUserURL = 'Services/Authentication.svc/REST/RegisterTempPatientWithoutSMS'; public static sendSMSForgotFileNoURL = 'Services/Authentication.svc/REST/SendPatientIDSMSByMobileNumber'; public static forgotFileIDURL = 'Services/Authentication.svc/REST/CheckActivationCodeForSendFileNo'; public static user: AuthenticatedUser; public static LOGIN_EVENT = 'user-login-event'; public static FAMILY_LOGIN_EVENT = 'family-login-event'; public static AUTHENTICATED_USER_KEY = 'save-authenticated-user'; // private static user: AuthenticatedUser; constructor( public con: ConnectorService, public cs: CommonService, public ts: TranslatorService, public nativeStorage: NativeStorage, public localNotifications: UserLocalNotificationService, private events: Events ) { } public authenticateRequest(request: Request, automaticLogin = true): Request { this.setPublicFields(request); const user = this.getAuthenticatedUser(); if (user) { /*user with eye prescriptions*/ request.PatientID = user.PatientID; request.TokenID = user.TokenID; request.PatientOutSA = user.PatientOutSA ? 1 : 0; request.PatientTypeID = user.PatientTypeID; if (AuthenticationService.requireRelogin) { this.sessionTimeOutDialog(); } } else { this.cs.userNeedToReLogin(); } /* else { if (automaticLogin) { this.cs.openUserLogin(); } } */ return request; } public setPublicFields(request: Request): Request { request.VersionID = 1; request.Channel = 31; request.LanguageID = TranslatorService.getCurrentLanguageCode(); //request.IPAdress = '10.10.10.10'; request.SessionID = 'any thing'; // ??? required for not authorized login funny request.MobileType = "android"; //request.isDentalAllowedBackend = false; return request; } public authenticateAndSetPersonalInformation(request: EmailRequest, examinationInfo?: any): EmailRequest { // if not authenticated will be redirected to login this.authenticateRequest(request); const user = this.getAuthenticatedUser(); if (user) { request.To = user.Email; request.DateofBirth = user.DateofBirth; request.PatientIditificationNum = user.PatientIdentificationNo; request.PatientMobileNumber = user.MobileNo; request.PatientName = user.PatientName; request.PatientOutSA = user.PatientOutSA ? 1 : 0; request.PatientTypeID = user.PatientTypeID; if (examinationInfo) { if (examinationInfo.SetupID) { request.SetupID = examinationInfo.SetupID; } if (examinationInfo.ProjectName) { request.ProjectName = examinationInfo.ProjectName; } if (examinationInfo.ClinicName) { request.ClinicName = examinationInfo.ClinicDescription; } if (examinationInfo.DoctorName) { request.DoctorName = examinationInfo.DoctorName; } if (examinationInfo.ProjectID) { request.ProjectID = examinationInfo.ProjectID; } if (examinationInfo.InvoiceNo) { request.InvoiceNo = examinationInfo.InvoiceNo; } if (examinationInfo.OrderDate) { request.OrderDate = this.cs.getDateISO(this.cs.evaluteDateAsObject(examinationInfo.OrderDate)); } } } return request; } public getAuthenticatedRequest(login = true): Request { const request = new Request(); this.authenticateRequest(request, login); return request; } public getPublicRequest(): Request { const request = new Request(); this.setPublicFields(request); return request; } public login(request: LoginRequest, onError: any, errorLabel: string): Observable { request.PatientID = 0; request.TokenID = ''; request.isDentalAllowedBackend = false; request.isRegister = false; return this.con.post(AuthenticationService.loginURL, request, onError, errorLabel); } public isAuthenticated(): boolean { return AuthenticationService.user != null; } public isAuthenticatedFamilyMember(): boolean { if ( AuthenticationService.user != null ) { return AuthenticationService.user.familyMember; } return false; } /** * this fucntion load from user information if he logged in before * and save user into memory and local storage * disable notifications for previous user if new user logged in with different user * * * info: * 1- user stored in local storage without token * @param result check activation code result */ public setAuthenticatedUser(result: CheckActivationCodeResponse): Observable { return Observable.create(observer => { this.loadAuthenticatedUser().subscribe((loadedUser: AuthenticatedUser) => { AuthenticationService.requireRelogin = false; this.startIdleMonitoring(); const user = this.updateAuthenticatedUser(result, loadedUser); /* we store in hd without token but with token in memory*/ this.saveUserInStorage(user).subscribe((success: boolean) => { AuthenticationService.user = user; this.publishUserChangeEvent(); observer.next(true); observer.complete(); }); }); }); } public resetAuthenticatedUser(user: AuthenticatedUser ) { AuthenticationService.user = user; AuthenticationService.requireRelogin = false; this.startIdleMonitoring(); this.publishUserChangeEvent(); } public setAuthenticatedMemberFamilyUser(result: CheckActivationCodeResponse) { const authUser = new AuthenticatedUser(); AuthenticationService.requireRelogin = false; this.startIdleMonitoring(); const user = this.updateAuthenticatedUser(result, authUser); user["familyMember"] = true; user.familyMember = true; // user["hello"]= "ok"; /* we store in hd without token but with token in memory*/ AuthenticationService.user = user; this.publishFamilyMemeberUserChangeEvent(); } public updateLoggedInUser(newUser: AuthenticatedUser): Observable { return Observable.create(observer => { /* we store in hd without token but with token in memory*/ this.saveUserInStorage(newUser).subscribe((success: boolean) => { AuthenticationService.requireRelogin = false; AuthenticationService.user = newUser; this.publishUserChangeEvent(); observer.next(success); observer.complete(); }); }); } public setDeviceToken(token: string) { const user = this.getAuthenticatedUser(); if (user) { user.deviceToken = token; this.updateLoggedInUser(user).subscribe((success: boolean) => { // console.log('token stored:' + token); }); } } public registerTempUser(request: RegisterInformationRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); request.TokenID = ''; return this.con.post(AuthenticationService.registerTempUserURL, request, onError, errorLabel); } public getLoginInfo(request: GetLoginInfoRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); return this.con.post(AuthenticationService.getLoginInfoURL, request, onError, errorLabel); } /** *update new authenticated user from previously stored and loaded user */ private updateAuthenticatedUser(result: CheckActivationCodeResponse, loadedUser: AuthenticatedUser): AuthenticatedUser { const user = result.List[0]; if (loadedUser) { // only if same user we fetch previous settings if (loadedUser.PatientID === user.PatientID) { user.agreed = loadedUser.agreed; user.biometricEnabled = loadedUser.biometricEnabled; } else { this.localNotifications.deleteAllNotifications(); } } if (user.FirstName && user.LastName) { user.PatientName = user.FirstName + ' ' + user.LastName; } user.IdentificationNo = user.PatientIdentificationNo; /* since suliman al habib has no support now for international phone number we do this woraround */ if (user.PatientOutSA) { user.ZipCode = InternationalMobileComponent.EMIRATE_DIAL_CODE; } else { user.ZipCode = InternationalMobileComponent.SAUDI_DIAL_CODE; } user.Email = this.isRealEmail(user.EmailAddress) ? user.EmailAddress : null; user.MobileNumber = user.MobileNumber.substr(1, user.MobileNumber.length - 1); user.MobileNo = user.MobileNumber; user.PatientOutSA = result.PatientOutSA; user.PatientTypeID = result.PatientType; user.TokenID = result.AuthenticationTokenID; return user; } public isRealEmail(email: string): boolean { return EmailInput.isValidEmail(email); } public isAuthenticatedUserHasRealEmail(): boolean { const user = this.getAuthenticatedUser(); return user ? this.isRealEmail(user.Email) : false; } public setUserAgreed(agreed: boolean) { const user = this.getAuthenticatedUser(); if (user) { user.agreed = agreed; // user['agreed'] = agreed; this.saveUserInStorage(user).subscribe(result => { }); } else { } } public isAgreedBefore(): boolean { const user = this.getAuthenticatedUser(); if (user) { return (user.agreed != null) ? user.agreed : false; } else { return false; } } /** * we store in localstorage without token but we keep it in the static member */ private saveUserInStorage(user: AuthenticatedUser): Observable { return Observable.create(observer => { if (user) { const TokenID = user.TokenID; user.TokenID = null; this.nativeStorage.setItem(AuthenticationService.AUTHENTICATED_USER_KEY, user) .then( () => { user.TokenID = TokenID; observer.next(true); observer.complete(); }, error => { user.TokenID = TokenID; observer.next(false); observer.complete(); } ); } else { observer.next(false); observer.complete(); } }); } /** * signout *clear user session and storage information */ public clearUser(): Observable { this.clearUserSession(); return Observable.create(observer => { this.publishUserChangeEvent(); this.nativeStorage.remove(AuthenticationService.AUTHENTICATED_USER_KEY).then( () => { observer.next(true); observer.complete(); }, () => { observer.next(false); observer.complete(); }); }); } private publishUserChangeEvent() { this.events.publish(AuthenticationService.LOGIN_EVENT, this.getAuthenticatedUser(), Date.now()); } private publishFamilyMemeberUserChangeEvent() { this.events.publish(AuthenticationService.FAMILY_LOGIN_EVENT, this.getAuthenticatedUser(), Date.now()); } public clearUserSession() { AuthenticationService.user = null; } public loadAuthenticatedUser(): Observable { return Observable.create(observer => { this.nativeStorage.getItem(AuthenticationService.AUTHENTICATED_USER_KEY) .then( (user) => { observer.next(user); observer.complete(); }, error => { observer.next(null); observer.complete(); } ); }); } // TODO you should read from local storage and update static member public getAuthenticatedUser(): AuthenticatedUser { /* if (AuthenticationService.requireRelogin) { this.sessionTimeOutDialog(); return new AuthenticatedUser(); } else { return AuthenticationService.user; } */ return AuthenticationService.user; } public checkUserAuthentication(request: CheckUserAuthenticationRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); return this.con.post(AuthenticationService.login, request, onError, errorLabel); } public checkActivationCode(request: CheckActivationCodeRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); return this.con.post(AuthenticationService.activationCodeURL, request, onError, errorLabel); } /* client side: id no , mobile no , zip code */ public checkPatientForRegisteration(request: CheckPatientRegisterationRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); request.TokenID = ''; request.PatientID = 0; request.isRegister = false; return this.con.post(AuthenticationService.checkPatientForRegisterationURL, request, onError, errorLabel); } /* client side: id no , mobile no , zip code */ public sendSmsForPatientRegisteration(request: CheckPatientRegisterationRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); request.TokenID = ''; request.PatientID = 0; request.isRegister = false; return this.con.post(AuthenticationService.sendSmsForRegisterURL, request, onError, errorLabel); } public checkRegisterationActivationCode(request: CheckRegisterationCodeRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); return this.con.post(AuthenticationService.activationCodeURL, request, onError, errorLabel); } public sendSMSForForgotFileNumber(request: CheckUserAuthenticationRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); request.TokenID = ''; request.PatientIdentificationID = ''; request.PatientID = 0; //request.SearchType = 2; //request.isRegister = false; return this.con.post(AuthenticationService.sendSMSForgotFileNoURL, request, onError, errorLabel); } public forgotFileIdActivation(request: CheckActivationCodeRequest, onError: any, errorLabel: string) : Observable { this.setPublicFields(request); request.TokenID = ''; request.PatientIdentificationID = ''; request.PatientID = 0; request.SearchType = 2; request.isRegister = false; return this.con.post(AuthenticationService.forgotFileIDURL, request, onError, errorLabel); } public isSAUDIIDValid(id: string): boolean { if (!id) { return false; } try { id = id.toString(); id = id.trim(); const returnValue = Number(id); let sum = 0; if (returnValue > 0) { const type = Number(id.charAt(0)); if (id.length !== 10) { return false; } if (type !== 2 && type !== 1) { return false; } for (let i = 0; i < 10; i++) { if ((i % 2) === 0) { const a = id.charAt(i); const x = Number(a) * 2; let b = x.toString(); if (b.length === 1) { b = '0' + b; } sum += Number(b.charAt(0)) + Number(b.charAt(1)); } else { sum += Number(id.charAt(i)); } } return ((sum % 10) === 0); } } catch (err) { } return false; } public checkUserHasEmailDialog(): Observable { return Observable.create(observer => { if (this.isAuthenticatedUserHasRealEmail()) { const message = this.ts.trPK('general', 'send-email') .replace('[0]', this.getAuthenticatedUser().EmailAddress || this.getAuthenticatedUser().Email); this.cs.presentConfirmDialog(message, () => { observer.next(); observer.complete(); }); } else { this.cs.presentConfirmDialog(this.ts.trPK('login', 'enter-email'), () => { //this.cs.openPatientProfile(); observer.complete(); }); } }); } /* user session timeout after idle for 20 Minute */ private static requireRelogin = false; public static monitorInterval_M = 20 * 60 * 1000; // public static monitorInterval_M = 15 * 1000; private static timerID; private stopIdleTimer() { if (AuthenticationService.timerID) { clearTimeout(AuthenticationService.timerID); } } private sessionTimeOutDialog() { this.cs.presentConfirmDialog(this.ts.trPK('general', 'idle-relogin'), () => { //this.cs.openUserLogin(); }); } /* reset and start idling interval */ private detectIdle() { this.stopIdleTimer(); AuthenticationService.timerID = setTimeout(() => { if (AuthenticationService.user) { AuthenticationService.requireRelogin = true; this.clearUserSession(); this.publishUserChangeEvent(); this.sessionTimeOutDialog(); this.cs.openHome(); } }, AuthenticationService.monitorInterval_M); } private registerDocumentEvents(events: string[], handler: any) { for (const event of events) { document.addEventListener(event, handler); } } private static eventsRegistered = false; public startIdleMonitoring() { if (!AuthenticationService.eventsRegistered) { AuthenticationService.eventsRegistered = true; // every time we have and event this.registerDocumentEvents(['mousedown', 'touchstart', 'click', 'keyup'], () => { // if user still logged in and session not expired if (AuthenticationService.user && !AuthenticationService.requireRelogin) { // reset and start idling timer this.detectIdle(); } }); } } }