import { ServerType } from '@sasjs/utils/types' import { RequestClient } from '../request/RequestClient' import { delay, serialize } from '../utils' export class AuthManager { public userName = '' private loginUrl: string private logoutUrl: string constructor( private serverUrl: string, private serverType: ServerType, private requestClient: RequestClient, private loginCallback: () => Promise ) { this.loginUrl = `/SASLogon/login` this.logoutUrl = this.serverType === ServerType.Sas9 ? '/SASLogon/logout?' : '/SASLogon/logout.do?' } public async redirectedLogIn() { await this.logOut() const loginPopup = window.open( this.loginUrl.replace('.do', ''), '_blank', 'toolbar=0,location=0,menubar=0,width=500,height=500' ) let isLoggedIn = false let startTime = new Date() let elapsedSeconds = 0 do { await delay(1000) isLoggedIn = document.cookie.includes('Current-User') && document.cookie.includes('userId') elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000 } while (!isLoggedIn && elapsedSeconds < 5 * 60) let isAuthorized = false startTime = new Date() do { await delay(1000) isAuthorized = !loginPopup?.window.location.href.includes('SASLogon') elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000 } while (!isAuthorized && elapsedSeconds < 5 * 60) loginPopup?.close() return { isLoggedIn: isLoggedIn && isAuthorized, userName: 'test' } } /** * Logs into the SAS server with the supplied credentials. * @param username - a string representing the username. * @param password - a string representing the password. */ public async logIn(username: string, password: string) { const loginParams: any = { _service: 'default', username, password } this.userName = loginParams.username const { isLoggedIn, loginForm } = await this.checkSession() if (isLoggedIn) { await this.loginCallback() return { isLoggedIn, userName: this.userName } } let loginResponse = await this.sendLoginRequest(loginForm, loginParams) let loggedIn = isLogInSuccess(loginResponse) if (!loggedIn) { if (isCredentialsVerifyError(loginResponse)) { const newLoginForm = await this.getLoginForm(loginResponse) loginResponse = await this.sendLoginRequest(newLoginForm, loginParams) } const currentSession = await this.checkSession() loggedIn = currentSession.isLoggedIn } if (loggedIn) { if (this.serverType === ServerType.Sas9) { const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check` await this.requestClient.get( `/SASLogon/login?service=${casAuthenticationUrl}`, undefined ) } this.loginCallback() } return { isLoggedIn: !!loggedIn, userName: this.userName } } private async sendLoginRequest( loginForm: { [key: string]: any }, loginParams: { [key: string]: any } ) { for (const key in loginForm) { loginParams[key] = loginForm[key] } const loginParamsStr = serialize(loginParams) const { result: loginResponse } = await this.requestClient.post( this.loginUrl, loginParamsStr, undefined, 'text/plain', { 'Content-Type': 'application/x-www-form-urlencoded', Accept: '*/*' } ) return loginResponse } /** * Checks whether a session is active, or login is required. * @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`. */ public async checkSession() { //For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution. //For SAS9 we will send request on SASStoredProcess const url = this.serverType === 'SASVIYA' ? `${this.serverUrl}/identities` : `${this.serverUrl}/SASStoredProcess` const { result: loginResponse } = await this.requestClient .get(url, undefined, 'text/plain') .catch((err: any) => { return { result: 'authErr' } }) const isLoggedIn = loginResponse !== 'authErr' let loginForm = null if (!isLoggedIn) { //We will logout to make sure cookies are removed and login form is presented //Residue can happen in case of session expiration await this.logOut() const { result: formResponse } = await this.requestClient.get( this.loginUrl.replace('.do', ''), undefined, 'text/plain' ) loginForm = await this.getLoginForm(formResponse) } return Promise.resolve({ isLoggedIn, userName: this.userName, loginForm }) } private getLoginForm(response: any) { const pattern: RegExp = // const matches = pattern.exec(response) const formInputs: any = {} if (matches && matches.length) { this.setLoginUrl(matches) const inputs = response.match(/]*>/g) if (inputs) { inputs.forEach((inputStr: string) => { const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/) if (valueMatch && valueMatch.length) { formInputs[valueMatch[1]] = valueMatch[2] } }) } } return Object.keys(formInputs).length ? formInputs : null } private setLoginUrl = (matches: RegExpExecArray) => { let parsedURL = matches[1].replace(/\?.*/, '') if (parsedURL[0] === '/') { parsedURL = parsedURL.substr(1) const tempLoginLink = this.serverUrl ? `${this.serverUrl}/${parsedURL}` : `${parsedURL}` const loginUrl = tempLoginLink this.loginUrl = this.serverType === ServerType.SasViya ? tempLoginLink : loginUrl.replace('.do', '') } } /** * Logs out of the configured SAS server. */ public logOut() { this.requestClient.clearCsrfTokens() return this.requestClient.get(this.logoutUrl, undefined).then(() => true) } } const isCredentialsVerifyError = (response: string): boolean => /An error occurred while the system was verifying your credentials. Please enter your credentials again./gm.test( response ) const isLogInSuccess = (response: string): boolean => /You have signed in/gm.test(response)