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:
164
src/SASjs.ts
164
src/SASjs.ts
@@ -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
205
src/auth/auth.ts
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user