1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-10 13:50:05 +00:00

fix(*): extracted auth logic into separate class, used axios instead of fetch

This commit is contained in:
Krishna Acondy
2021-01-18 08:59:58 +00:00
parent c0b82c5125
commit f789b8f7a2
2 changed files with 216 additions and 153 deletions

View File

@@ -11,12 +11,8 @@ require('isomorphic-fetch')
import { import {
convertToCSV, convertToCSV,
compareTimestamps, compareTimestamps,
serialize,
isAuthorizeFormRequired,
parseAndSubmitAuthorizeForm,
splitChunks, splitChunks,
isLogInRequired, isLogInRequired,
isLogInSuccess,
parseSourceCode, parseSourceCode,
parseGeneratedCode, parseGeneratedCode,
parseWeboutResponse, parseWeboutResponse,
@@ -38,6 +34,7 @@ import {
import { SASViyaApiClient } from './SASViyaApiClient' import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient' import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from './FileUploader' import { FileUploader } from './FileUploader'
import { AuthManager } from './auth/auth'
const defaultConfig: SASjsConfig = { const defaultConfig: SASjsConfig = {
serverUrl: '', serverUrl: '',
@@ -59,8 +56,6 @@ const requestRetryLimit = 5
export default class SASjs { export default class SASjs {
private sasjsConfig: SASjsConfig = new SASjsConfig() private sasjsConfig: SASjsConfig = new SASjsConfig()
private jobsPath: string = '' private jobsPath: string = ''
private logoutUrl: string = ''
private loginUrl: string = ''
private csrfTokenApi: CsrfToken | null = null private csrfTokenApi: CsrfToken | null = null
private csrfTokenWeb: CsrfToken | null = null private csrfTokenWeb: CsrfToken | null = null
private retryCountWeb: number = 0 private retryCountWeb: number = 0
@@ -68,10 +63,10 @@ export default class SASjs {
private retryCountJeseApi: number = 0 private retryCountJeseApi: number = 0
private sasjsRequests: SASjsRequest[] = [] private sasjsRequests: SASjsRequest[] = []
private sasjsWaitingRequests: SASjsWaitingRequest[] = [] private sasjsWaitingRequests: SASjsWaitingRequest[] = []
private userName: string = ''
private sasViyaApiClient: SASViyaApiClient | null = null private sasViyaApiClient: SASViyaApiClient | null = null
private sas9ApiClient: SAS9ApiClient | null = null private sas9ApiClient: SAS9ApiClient | null = null
private fileUploader: FileUploader | null = null private fileUploader: FileUploader | null = null
private authManager: AuthManager | null = null
constructor(config?: any) { constructor(config?: any) {
this.sasjsConfig = { this.sasjsConfig = {
@@ -433,7 +428,7 @@ export default class SASjs {
* *
*/ */
public getUserName() { public getUserName() {
return this.userName return this.authManager!.userName
} }
/** /**
@@ -475,48 +470,12 @@ export default class SASjs {
} }
} }
private async getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const matches = pattern.exec(response)
const formInputs: any = {}
if (matches && matches.length) {
this.setLoginUrl(matches)
const inputs = response.match(/<input.*"hidden"[^>]*>/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
}
/** /**
* Checks whether a session is active, or login is required. * 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`. * @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
*/ */
public async checkSession() { public async checkSession() {
const loginResponse = await fetch(this.loginUrl.replace('.do', '')) return this.authManager!.checkSession()
const responseText = await loginResponse.text()
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
let loginForm: any = null
if (!isLoggedIn) {
loginForm = await this.getLoginForm(responseText)
}
return Promise.resolve({
isLoggedIn,
userName: this.userName,
loginForm
})
} }
/** /**
@@ -525,81 +484,14 @@ export default class SASjs {
* @param password - a string representing the password. * @param password - a string representing the password.
*/ */
public async logIn(username: string, password: string) { public async logIn(username: string, password: string) {
const loginParams: any = { return this.authManager!.logIn(username, password)
_service: 'default',
username,
password
}
this.userName = loginParams.username
const { isLoggedIn, loginForm } = await this.checkSession()
if (isLoggedIn) {
this.resendWaitingRequests()
return Promise.resolve({
isLoggedIn,
userName: this.userName
})
}
for (const key in loginForm) {
loginParams[key] = loginForm[key]
}
const loginParamsStr = serialize(loginParams)
return fetch(this.loginUrl, {
method: 'POST',
credentials: 'include',
referrerPolicy: 'same-origin',
body: loginParamsStr,
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
})
.then((response) => response.text())
.then(async (responseText) => {
let authFormRes: any
let loggedIn
if (isAuthorizeFormRequired(responseText)) {
authFormRes = await parseAndSubmitAuthorizeForm(
responseText,
this.sasjsConfig.serverUrl
)
} else {
loggedIn = isLogInSuccess(responseText)
}
if (!loggedIn) {
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
this.resendWaitingRequests()
}
return {
isLoggedIn: loggedIn,
userName: this.userName
}
})
.catch((e) => Promise.reject(e))
} }
/** /**
* Logs out of the configured SAS server. * Logs out of the configured SAS server.
*/ */
public logOut() { public logOut() {
return new Promise((resolve, reject) => { return this.authManager!.logOut()
const logOutURL = `${this.sasjsConfig.serverUrl}${this.logoutUrl}`
fetch(logOutURL)
.then(() => {
resolve(true)
})
.catch((err: Error) => reject(err))
})
} }
/** /**
@@ -1174,7 +1066,6 @@ export default class SASjs {
this.sasjsWaitingRequests.push(sasjsWaitingRequest) this.sasjsWaitingRequests.push(sasjsWaitingRequest)
} else { } else {
if (config.serverType === ServerType.SAS9 && config.debug) { if (config.serverType === ServerType.SAS9 && config.debug) {
this.updateUsername(responseText)
const jsonResponseText = parseWeboutResponse(responseText) const jsonResponseText = parseWeboutResponse(responseText)
if (jsonResponseText !== '') { if (jsonResponseText !== '') {
@@ -1194,7 +1085,6 @@ export default class SASjs {
try { try {
this.parseSASVIYADebugResponse(responseText).then( this.parseSASVIYADebugResponse(responseText).then(
(resText: any) => { (resText: any) => {
this.updateUsername(resText)
try { try {
resolve(JSON.parse(resText)) resolve(JSON.parse(resText))
} catch (e) { } catch (e) {
@@ -1224,7 +1114,6 @@ export default class SASjs {
) )
} }
} else { } else {
this.updateUsername(responseText)
if ( if (
responseText.includes( responseText.includes(
'The requested URL /SASStoredProcess/do/ was not found on this server.' 'The requested URL /SASStoredProcess/do/ was not found on this server.'
@@ -1304,19 +1193,6 @@ export default class SASjs {
return requestParams return requestParams
} }
private updateUsername(response: any) {
try {
const responseJson = JSON.parse(response)
if (this.sasjsConfig.serverType === ServerType.SAS9) {
this.userName = responseJson['_METAUSER']
} else {
this.userName = responseJson['SYSUSERID']
}
} catch (e) {
this.userName = ''
}
}
private parseSASVIYADebugResponse(response: string) { private parseSASVIYADebugResponse(response: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const iframeStart = response.split( const iframeStart = response.split(
@@ -1527,11 +1403,11 @@ export default class SASjs {
this.sasjsConfig.serverType === ServerType.SASViya this.sasjsConfig.serverType === ServerType.SASViya
? this.sasjsConfig.pathSASViya ? this.sasjsConfig.pathSASViya
: this.sasjsConfig.pathSAS9 : this.sasjsConfig.pathSAS9
this.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login` this.authManager = new AuthManager(
this.logoutUrl = this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType === ServerType.SAS9 this.sasjsConfig.serverType!,
? '/SASLogon/logout?' this.resendWaitingRequests
: '/SASLogon/logout.do?' )
if (this.sasjsConfig.serverType === ServerType.SASViya) { if (this.sasjsConfig.serverType === ServerType.SASViya) {
if (this.sasViyaApiClient) if (this.sasViyaApiClient)
@@ -1563,24 +1439,6 @@ export default class SASjs {
) )
} }
private setLoginUrl = (matches: RegExpExecArray) => {
let parsedURL = matches[1].replace(/\?.*/, '')
if (parsedURL[0] === '/') {
parsedURL = parsedURL.substr(1)
const tempLoginLink = this.sasjsConfig.serverUrl
? `${this.sasjsConfig.serverUrl}/${parsedURL}`
: `${parsedURL}`
const loginUrl = tempLoginLink
this.loginUrl =
this.sasjsConfig.serverType === ServerType.SASViya
? tempLoginLink
: loginUrl.replace('.do', '')
}
}
private async createFoldersAndServices( private async createFoldersAndServices(
parentFolder: string, parentFolder: string,
membersJson: any[], membersJson: any[],

205
src/auth/auth.ts Normal file
View File

@@ -0,0 +1,205 @@
import axios, { AxiosInstance } from 'axios'
import { ServerType } from '../types'
import {
serialize,
isAuthorizeFormRequired,
parseAndSubmitAuthorizeForm,
isLogInSuccess
} from '../utils'
export class AuthManager {
public userName = ''
private loginUrl: string
private logoutUrl: string
private httpClient: AxiosInstance
constructor(
private serverUrl: string,
private serverType: ServerType,
private loginCallback: Function
) {
this.httpClient = axios.create({ baseURL: this.serverUrl })
this.loginUrl = `/SASLogon/login`
this.logoutUrl =
this.serverType === ServerType.SAS9
? '/SASLogon/logout?'
: '/SASLogon/logout.do?'
}
/**
* 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) {
this.loginCallback()
return {
isLoggedIn,
userName: this.userName
}
}
for (const key in loginForm) {
loginParams[key] = loginForm[key]
}
const loginParamsStr = serialize(loginParams)
const loginResponse = await axios
.post<string>(this.loginUrl, loginParamsStr, {
withCredentials: true,
responseType: 'text',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
.then((response) => response.data)
let loggedIn
if (isAuthorizeFormRequired(loginResponse)) {
await parseAndSubmitAuthorizeForm(loginResponse, this.serverUrl)
} else {
loggedIn = isLogInSuccess(loginResponse)
}
if (!loggedIn) {
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
this.loginCallback()
}
return {
isLoggedIn: !!loggedIn,
userName: this.userName
}
return {
isLoggedIn: isLogInSuccess(loginResponse),
userName: this.userName
}
return fetch(this.loginUrl, {
method: 'POST',
credentials: 'include',
referrerPolicy: 'same-origin',
body: loginParamsStr,
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
})
.then((response) => response.text())
.then(async (responseText) => {
let loggedIn
if (isAuthorizeFormRequired(responseText)) {
const authFormResponse = await parseAndSubmitAuthorizeForm(
responseText,
this.serverUrl
)
} else {
loggedIn = isLogInSuccess(responseText)
}
if (!loggedIn) {
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
this.loginCallback()
}
return {
isLoggedIn: loggedIn,
userName: this.userName
}
})
.catch((e) => Promise.reject(e))
}
/**
* 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() {
const loginResponse = await fetch(this.loginUrl.replace('.do', ''))
const responseText = await loginResponse.text()
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
let loginForm: any = null
if (!isLoggedIn) {
loginForm = await this.getLoginForm(responseText)
}
return Promise.resolve({
isLoggedIn,
userName: this.userName,
loginForm
})
}
private async getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const matches = pattern.exec(response)
const formInputs: any = {}
if (matches && matches.length) {
this.setLoginUrl(matches)
const inputs = response.match(/<input.*"hidden"[^>]*>/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() {
return new Promise((resolve, reject) => {
fetch(this.logoutUrl)
.then(() => {
resolve(true)
})
.catch((err: Error) => reject(err))
})
}
}