mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a181914c36 | ||
|
|
539405e249 | ||
|
|
d9c27efa8d | ||
|
|
4623b9665b | ||
|
|
3ae0809ee5 | ||
|
|
0ea6e839ac | ||
|
|
a00bf5ba67 | ||
|
|
e0b09adbba | ||
|
|
19a57dbf6e | ||
|
|
cd2b32f2f4 | ||
|
|
a1f5355d6a | ||
|
|
0972c0deaa | ||
|
|
e1a5cc9e45 | ||
|
|
351a22cb3c | ||
|
|
3ccd35a4e2 | ||
|
|
e4956cc1d4 | ||
|
|
291ba51b07 | ||
|
|
ed72c5c48c | ||
|
|
ad4eead4ca | ||
|
|
f40a86f0f6 | ||
|
|
aa9383a483 | ||
|
|
867422f4cc | ||
|
|
2a6e29b5b8 | ||
|
|
ba105f609c | ||
|
|
e4d669f9b6 | ||
|
|
5edf09e0a7 | ||
|
|
5a695f495c | ||
|
|
f231edb4a6 | ||
|
|
389ef94cd5 | ||
|
|
4c90f66dbc | ||
| ab8643a89a | |||
|
|
b831b93133 | ||
|
|
0c3aab673a | ||
|
|
83353326fb | ||
|
|
db7a5d601e | ||
|
|
ee977f4fab | ||
| 33e7564e8f | |||
|
|
1a59f95be7 | ||
| 77c4c473c1 | |||
| 47ff1a2293 | |||
|
|
97918f301b | ||
|
|
830a907bd1 | ||
|
|
ffae344476 | ||
|
|
fc1c93957c |
@@ -1,4 +1,5 @@
|
||||
sasjs-tests/
|
||||
docs/
|
||||
.github/
|
||||
CONTRIBUTING.md
|
||||
*.md
|
||||
*.spec.ts
|
||||
|
||||
16
checkNodeVersion.js
Normal file
16
checkNodeVersion.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const result = process.versions
|
||||
if (result && result.node) {
|
||||
if (parseInt(result.node) < 14) {
|
||||
console.log(
|
||||
'\x1b[31m%s\x1b[0m',
|
||||
`❌ Process failed due to Node Version,\nPlease install and use Node Version >= 14\nYour current Node Version is: ${result.node}`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
'\x1b[31m%s\x1b[0m',
|
||||
'Something went wrong while checking Node version'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
18428
package-lock.json
generated
18428
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -3,8 +3,10 @@
|
||||
"description": "JavaScript adapter for SAS",
|
||||
"homepage": "https://adapter.sasjs.io",
|
||||
"scripts": {
|
||||
"preinstall": "node checkNodeVersion",
|
||||
"prebuild": "node checkNodeVersion",
|
||||
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
|
||||
"package:lib": "npm run build && copyfiles ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
||||
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
|
||||
"publish:lib": "npm run build && cd build && npm publish",
|
||||
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
||||
@@ -40,41 +42,38 @@
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/form-data": "^2.5.0",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/mime": "^2.0.3",
|
||||
"@types/tough-cookie": "^4.0.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cp": "^0.2.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.0.6",
|
||||
"jest": "^27.1.0",
|
||||
"jest-extended": "^0.11.5",
|
||||
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||
"path": "^0.12.7",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "^17.4.4",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"semantic-release": "^17.4.7",
|
||||
"terser-webpack-plugin": "^5.2.0",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^9.2.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.21.4",
|
||||
"typedoc": "^0.21.9",
|
||||
"typedoc-neo-theme": "^1.1.1",
|
||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||
"typescript": "^4.3.5",
|
||||
"typescript": "4.3.5",
|
||||
"webpack": "^5.44.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "^2.27.1",
|
||||
"@sasjs/utils": "^2.30.0",
|
||||
"axios": "^0.21.1",
|
||||
"axios-cookiejar-support": "^1.0.1",
|
||||
"form-data": "^4.0.0",
|
||||
"https": "^1.0.0",
|
||||
"tough-cookie": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=15"
|
||||
}
|
||||
}
|
||||
|
||||
22129
sasjs-tests/package-lock.json
generated
22129
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||
import SASjs, { LoginMechanism, SASjsConfig } from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
|
||||
@@ -13,7 +13,8 @@ const defaultConfig: SASjsConfig = {
|
||||
debug: false,
|
||||
contextName: 'SAS Job Execution compute context',
|
||||
useComputeApi: false,
|
||||
allowInsecureRequests: false
|
||||
allowInsecureRequests: false,
|
||||
loginMechanism: LoginMechanism.Default
|
||||
}
|
||||
|
||||
const customConfig = {
|
||||
@@ -41,6 +42,19 @@ export const basicTests = (
|
||||
assertion: (response: any) =>
|
||||
response && response.isLoggedIn && response.userName === userName
|
||||
},
|
||||
{
|
||||
title: 'Fetch username for already logged in user',
|
||||
description: 'Should log the user in',
|
||||
test: async () => {
|
||||
await adapter.logIn(userName, password)
|
||||
|
||||
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
|
||||
|
||||
return await newAdapterIns.checkSession()
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
response?.isLoggedIn && response?.userName === userName
|
||||
},
|
||||
{
|
||||
title: 'Multiple Log in attempts',
|
||||
description:
|
||||
@@ -48,7 +62,7 @@ export const basicTests = (
|
||||
test: async () => {
|
||||
await adapter.logOut()
|
||||
await adapter.logIn('invalid', 'invalid')
|
||||
return adapter.logIn(userName, password)
|
||||
return await adapter.logIn(userName, password)
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
response && response.isLoggedIn && response.userName === userName
|
||||
@@ -151,7 +165,7 @@ export const basicTests = (
|
||||
description:
|
||||
'Should complete successful request with extra attributes present in response',
|
||||
test: async () => {
|
||||
const config = {
|
||||
const config: Partial<SASjsConfig> = {
|
||||
useComputeApi: false
|
||||
}
|
||||
|
||||
|
||||
35
src/SASjs.ts
35
src/SASjs.ts
@@ -1,5 +1,11 @@
|
||||
import { compareTimestamps, asyncForEach } from './utils'
|
||||
import { SASjsConfig, UploadFile, EditContextInput, PollOptions } from './types'
|
||||
import {
|
||||
SASjsConfig,
|
||||
UploadFile,
|
||||
EditContextInput,
|
||||
PollOptions,
|
||||
LoginMechanism
|
||||
} from './types'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
import { FileUploader } from './FileUploader'
|
||||
@@ -19,6 +25,7 @@ import {
|
||||
Sas9JobExecutor
|
||||
} from './job-execution'
|
||||
import { ErrorResponse } from './types/errors'
|
||||
import { LoginOptions, LoginResult } from './types/Login'
|
||||
|
||||
const defaultConfig: SASjsConfig = {
|
||||
serverUrl: '',
|
||||
@@ -29,7 +36,8 @@ const defaultConfig: SASjsConfig = {
|
||||
debug: false,
|
||||
contextName: 'SAS Job Execution compute context',
|
||||
useComputeApi: null,
|
||||
allowInsecureRequests: false
|
||||
allowInsecureRequests: false,
|
||||
loginMechanism: LoginMechanism.Default
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,8 +534,27 @@ export default class SASjs {
|
||||
* @param username - a string representing the username.
|
||||
* @param password - a string representing the password.
|
||||
*/
|
||||
public async logIn(username: string, password: string) {
|
||||
return this.authManager!.logIn(username, password)
|
||||
public async logIn(
|
||||
username?: string,
|
||||
password?: string,
|
||||
options: LoginOptions = {}
|
||||
): Promise<LoginResult> {
|
||||
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
||||
if (!username || !password) {
|
||||
throw new Error(
|
||||
'A username and password are required when using the default login mechanism.'
|
||||
)
|
||||
}
|
||||
return this.authManager!.logIn(username, password)
|
||||
}
|
||||
|
||||
if (typeof window === typeof undefined) {
|
||||
throw new Error(
|
||||
'The redirected login mechanism is only available for use in the browser.'
|
||||
)
|
||||
}
|
||||
|
||||
return this.authManager!.redirectedLogIn(options)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getTokens } from '../../auth/getTokens'
|
||||
import { RequestClient } from '../../request/RequestClient'
|
||||
import { JobStatePollError } from '../../types/errors'
|
||||
import { Link, WriteStream } from '../../types'
|
||||
import { isNode } from '../../utils'
|
||||
import { delay, isNode } from '../../utils'
|
||||
|
||||
export async function pollJobState(
|
||||
requestClient: RequestClient,
|
||||
@@ -246,5 +246,3 @@ const doPoll = async (
|
||||
|
||||
return { state, pollCount }
|
||||
}
|
||||
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { LoginOptions, LoginResult } from '../types/Login'
|
||||
import { serialize } from '../utils'
|
||||
import { openWebPage } from './openWebPage'
|
||||
import { verifySas9Login } from './verifySas9Login'
|
||||
import { verifySasViyaLogin } from './verifySasViyaLogin'
|
||||
|
||||
export class AuthManager {
|
||||
public userName = ''
|
||||
private loginUrl: string
|
||||
private logoutUrl: string
|
||||
private redirectedLoginUrl = `/SASLogon/home`
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private serverType: ServerType,
|
||||
@@ -19,65 +24,137 @@ export class AuthManager {
|
||||
: '/SASLogon/logout.do?'
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens Pop up window to SAS Login screen.
|
||||
* And checks if user has finished login process.
|
||||
*/
|
||||
public async redirectedLogIn({
|
||||
onLoggedOut
|
||||
}: LoginOptions): Promise<LoginResult> {
|
||||
const { isLoggedIn: isLoggedInAlready, userName: currentSessionUsername } =
|
||||
await this.fetchUserName()
|
||||
|
||||
if (isLoggedInAlready) {
|
||||
await this.loginCallback()
|
||||
|
||||
return {
|
||||
isLoggedIn: true,
|
||||
userName: currentSessionUsername
|
||||
}
|
||||
}
|
||||
|
||||
const loginPopup = await openWebPage(
|
||||
this.redirectedLoginUrl,
|
||||
'SASLogon',
|
||||
{
|
||||
width: 500,
|
||||
height: 600
|
||||
},
|
||||
onLoggedOut
|
||||
)
|
||||
|
||||
if (!loginPopup) {
|
||||
return { isLoggedIn: false, userName: '' }
|
||||
}
|
||||
|
||||
const { isLoggedIn } =
|
||||
this.serverType === ServerType.SasViya
|
||||
? await verifySasViyaLogin(loginPopup)
|
||||
: await verifySas9Login(loginPopup)
|
||||
|
||||
loginPopup.close()
|
||||
|
||||
if (isLoggedIn) {
|
||||
if (this.serverType === ServerType.Sas9) {
|
||||
await this.performCASSecurityCheck()
|
||||
}
|
||||
|
||||
const { userName } = await this.fetchUserName()
|
||||
|
||||
await this.loginCallback()
|
||||
|
||||
return { isLoggedIn: true, userName }
|
||||
}
|
||||
|
||||
return { isLoggedIn: false, userName: '' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into the SAS server with the supplied credentials.
|
||||
* @param username - a string representing the username.
|
||||
* @param password - a string representing the password.
|
||||
* @returns - a boolean `isLoggedin` and a string `username`
|
||||
*/
|
||||
public async logIn(username: string, password: string) {
|
||||
const loginParams: any = {
|
||||
public async logIn(username: string, password: string): Promise<LoginResult> {
|
||||
const loginParams = {
|
||||
_service: 'default',
|
||||
username,
|
||||
password
|
||||
}
|
||||
|
||||
this.userName = loginParams.username
|
||||
let {
|
||||
isLoggedIn: isLoggedInAlready,
|
||||
loginForm,
|
||||
userName: currentSessionUsername
|
||||
} = await this.checkSession()
|
||||
|
||||
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||
if (isLoggedInAlready) {
|
||||
if (currentSessionUsername === loginParams.username) {
|
||||
await this.loginCallback()
|
||||
|
||||
if (isLoggedIn) {
|
||||
await this.loginCallback()
|
||||
|
||||
return {
|
||||
isLoggedIn,
|
||||
userName: this.userName
|
||||
this.userName = currentSessionUsername!
|
||||
return {
|
||||
isLoggedIn: true,
|
||||
userName: this.userName
|
||||
}
|
||||
} else {
|
||||
await this.logOut()
|
||||
loginForm = await this.getNewLoginForm()
|
||||
}
|
||||
}
|
||||
} else this.userName = ''
|
||||
|
||||
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
||||
|
||||
let loggedIn = isLogInSuccess(loginResponse)
|
||||
let isLoggedIn = isLogInSuccess(loginResponse)
|
||||
|
||||
if (!loggedIn) {
|
||||
if (!isLoggedIn) {
|
||||
if (isCredentialsVerifyError(loginResponse)) {
|
||||
const newLoginForm = await this.getLoginForm(loginResponse)
|
||||
|
||||
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
||||
}
|
||||
|
||||
const currentSession = await this.checkSession()
|
||||
loggedIn = currentSession.isLoggedIn
|
||||
const res = await this.checkSession()
|
||||
isLoggedIn = res.isLoggedIn
|
||||
|
||||
if (isLoggedIn) this.userName = res.userName
|
||||
} else {
|
||||
this.userName = loginParams.username
|
||||
}
|
||||
|
||||
if (loggedIn) {
|
||||
if (isLoggedIn) {
|
||||
if (this.serverType === ServerType.Sas9) {
|
||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||
|
||||
await this.requestClient.get<string>(
|
||||
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
||||
undefined
|
||||
)
|
||||
await this.performCASSecurityCheck()
|
||||
}
|
||||
|
||||
this.loginCallback()
|
||||
}
|
||||
} else this.userName = ''
|
||||
|
||||
return {
|
||||
isLoggedIn: !!loggedIn,
|
||||
isLoggedIn,
|
||||
userName: this.userName
|
||||
}
|
||||
}
|
||||
|
||||
private async performCASSecurityCheck() {
|
||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||
|
||||
await this.requestClient.get<string>(
|
||||
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
||||
undefined
|
||||
)
|
||||
}
|
||||
|
||||
private async sendLoginRequest(
|
||||
loginForm: { [key: string]: any },
|
||||
loginParams: { [key: string]: any }
|
||||
@@ -103,14 +180,53 @@ export class AuthManager {
|
||||
|
||||
/**
|
||||
* 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 three values
|
||||
* - a boolean `isLoggedIn`
|
||||
* - a string `userName` and
|
||||
* - a form `loginForm` if not loggedin.
|
||||
*/
|
||||
public async checkSession() {
|
||||
public async checkSession(): Promise<{
|
||||
isLoggedIn: boolean
|
||||
userName: string
|
||||
loginForm?: any
|
||||
}> {
|
||||
const { isLoggedIn, userName } = await this.fetchUserName()
|
||||
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()
|
||||
|
||||
loginForm = await this.getNewLoginForm()
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
isLoggedIn,
|
||||
userName: userName.toLowerCase(),
|
||||
loginForm
|
||||
})
|
||||
}
|
||||
|
||||
private async getNewLoginForm() {
|
||||
const { result: formResponse } = await this.requestClient.get<string>(
|
||||
this.loginUrl.replace('.do', ''),
|
||||
undefined,
|
||||
'text/plain'
|
||||
)
|
||||
|
||||
return await this.getLoginForm(formResponse)
|
||||
}
|
||||
|
||||
private async fetchUserName(): Promise<{
|
||||
isLoggedIn: boolean
|
||||
userName: string
|
||||
}> {
|
||||
//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.serverType === ServerType.SasViya
|
||||
? `${this.serverUrl}/identities/users/@currentUser`
|
||||
: `${this.serverUrl}/SASStoredProcess`
|
||||
|
||||
const { result: loginResponse } = await this.requestClient
|
||||
@@ -120,27 +236,27 @@ export class AuthManager {
|
||||
})
|
||||
|
||||
const isLoggedIn = loginResponse !== 'authErr'
|
||||
let loginForm = null
|
||||
const userName = isLoggedIn ? this.extractUserName(loginResponse) : ''
|
||||
|
||||
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()
|
||||
return { isLoggedIn, userName }
|
||||
}
|
||||
|
||||
const { result: formResponse } = await this.requestClient.get<string>(
|
||||
this.loginUrl.replace('.do', ''),
|
||||
undefined,
|
||||
'text/plain'
|
||||
)
|
||||
private extractUserName = (response: any): string => {
|
||||
switch (this.serverType) {
|
||||
case ServerType.SasViya:
|
||||
return response?.id
|
||||
|
||||
loginForm = await this.getLoginForm(formResponse)
|
||||
case ServerType.Sas9:
|
||||
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
|
||||
const username = matched?.[0].slice(17, -1)
|
||||
|
||||
if (!username.includes(' ')) return username
|
||||
|
||||
return username
|
||||
.split(' ')
|
||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
||||
.join('')
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
isLoggedIn,
|
||||
userName: this.userName,
|
||||
loginForm
|
||||
})
|
||||
}
|
||||
|
||||
private getLoginForm(response: any) {
|
||||
|
||||
40
src/auth/openWebPage.ts
Normal file
40
src/auth/openWebPage.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { openLoginPrompt } from '../utils/loginPrompt'
|
||||
|
||||
interface WindowFeatures {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
const defaultWindowFeatures: WindowFeatures = { width: 500, height: 600 }
|
||||
|
||||
export async function openWebPage(
|
||||
url: string,
|
||||
windowName: string = '',
|
||||
WindowFeatures: WindowFeatures = defaultWindowFeatures,
|
||||
onLoggedOut?: () => Promise<Boolean>
|
||||
): Promise<Window | null> {
|
||||
const { width, height } = WindowFeatures
|
||||
const left = screen.width / 2 - width / 2
|
||||
const top = screen.height / 2 - height / 2
|
||||
|
||||
const loginPopup = window.open(
|
||||
url,
|
||||
windowName,
|
||||
`toolbar=0,location=0,menubar=0,width=${width},height=${height},left=${left},top=${top}`
|
||||
)
|
||||
|
||||
if (!loginPopup) {
|
||||
const getUserAction: () => Promise<Boolean> = onLoggedOut ?? openLoginPrompt
|
||||
|
||||
const doLogin = await getUserAction()
|
||||
return doLogin
|
||||
? window.open(
|
||||
url,
|
||||
windowName,
|
||||
`toolbar=0,location=0,menubar=0,width=${width},height=${height},left=${left},top=${top}`
|
||||
)
|
||||
: null
|
||||
}
|
||||
|
||||
return loginPopup
|
||||
}
|
||||
@@ -3,10 +3,14 @@ import * as dotenv from 'dotenv'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
mockedCurrentUserApi,
|
||||
mockLoginAuthoriseRequiredResponse,
|
||||
mockLoginSuccessResponse
|
||||
} from './mockResponses'
|
||||
import { serialize } from '../../utils'
|
||||
import * as openWebPageModule from '../openWebPage'
|
||||
import * as verifySasViyaLoginModule from '../verifySasViyaLogin'
|
||||
import * as verifySas9LoginModule from '../verifySas9Login'
|
||||
import { RequestClient } from '../../request/RequestClient'
|
||||
jest.mock('axios')
|
||||
const mockedAxios = axios as jest.Mocked<typeof axios>
|
||||
@@ -57,134 +61,614 @@ describe('AuthManager', () => {
|
||||
expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout?')
|
||||
})
|
||||
|
||||
it('should call the auth callback and return when already logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName: 'test',
|
||||
loginForm: 'test'
|
||||
})
|
||||
)
|
||||
describe('login - default mechanism', () => {
|
||||
it('should call the auth callback and return when already logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName,
|
||||
loginForm: 'test'
|
||||
})
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should post a login request to the server if not logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: 'test',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
const loginParams = serialize({
|
||||
_service: 'default',
|
||||
username: userName,
|
||||
password,
|
||||
name: 'test'
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
`/SASLogon/login`,
|
||||
loginParams,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
|
||||
it('should post a login request to the server when already logged in with other username', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName: 'someOtherUsername',
|
||||
loginForm: null
|
||||
})
|
||||
)
|
||||
jest
|
||||
.spyOn(authManager, 'logOut')
|
||||
.mockImplementation(() => Promise.resolve(true))
|
||||
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'getNewLoginForm')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
name: 'test'
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
const loginParams = serialize({
|
||||
_service: 'default',
|
||||
username: userName,
|
||||
password,
|
||||
name: 'test'
|
||||
})
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
expect(authManager.logOut).toHaveBeenCalledTimes(1)
|
||||
expect(authManager['getNewLoginForm']).toHaveBeenCalledTimes(1)
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
`/SASLogon/login`,
|
||||
loginParams,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should post a login request to the server when not logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: '',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
const loginParams = serialize({
|
||||
_service: 'default',
|
||||
username: userName,
|
||||
password,
|
||||
name: 'test'
|
||||
})
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
`/SASLogon/login`,
|
||||
loginParams,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should post a login & a cas_security request to the SAS9 server when not logged in', async () => {
|
||||
const serverType = ServerType.Sas9
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: '',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
||||
)
|
||||
mockedAxios.get.mockImplementation(() => Promise.resolve({ status: 200 }))
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
const loginParams = serialize({
|
||||
_service: 'default',
|
||||
username: userName,
|
||||
password,
|
||||
name: 'test'
|
||||
})
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
`/SASLogon/login`,
|
||||
loginParams,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
}
|
||||
}
|
||||
)
|
||||
const casAuthenticationUrl = `${serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||
expect(mockedAxios.get).toHaveBeenCalledWith(
|
||||
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
||||
getHeadersJson
|
||||
)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should return empty username if unable to logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: '',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementation(() =>
|
||||
Promise.resolve({ data: 'Not Signed in' })
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.logIn(userName, password)
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
||||
expect(loginResponse.userName).toEqual('')
|
||||
|
||||
const loginParams = serialize({
|
||||
_service: 'default',
|
||||
username: userName,
|
||||
password,
|
||||
name: 'test'
|
||||
})
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
`/SASLogon/login`,
|
||||
loginParams,
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should parse and submit the authorisation form when necessary', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn(requestClient, 'authorize')
|
||||
.mockImplementation(() => Promise.resolve())
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: 'test',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: mockLoginAuthoriseRequiredResponse,
|
||||
config: { url: 'https://test.com/SASLogon/login' },
|
||||
request: { responseURL: 'https://test.com/OAuth/authorize' }
|
||||
})
|
||||
)
|
||||
|
||||
mockedAxios.get.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: mockLoginAuthoriseRequiredResponse
|
||||
})
|
||||
)
|
||||
|
||||
await authManager.logIn(userName, password)
|
||||
|
||||
expect(requestClient.authorize).toHaveBeenCalledWith(
|
||||
mockLoginAuthoriseRequiredResponse
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse and submit the authorisation form when necessary', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn(requestClient, 'authorize')
|
||||
.mockImplementation(() => Promise.resolve())
|
||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: 'test',
|
||||
loginForm: { name: 'test' }
|
||||
})
|
||||
)
|
||||
mockedAxios.post.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: mockLoginAuthoriseRequiredResponse,
|
||||
config: { url: 'https://test.com/SASLogon/login' },
|
||||
request: { responseURL: 'https://test.com/OAuth/authorize' }
|
||||
})
|
||||
)
|
||||
describe('login - redirect mechanism', () => {
|
||||
beforeAll(() => {
|
||||
jest.mock('../openWebPage')
|
||||
jest
|
||||
.spyOn(openWebPageModule, 'openWebPage')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ close: jest.fn() } as unknown as Window)
|
||||
)
|
||||
jest.mock('../verifySasViyaLogin')
|
||||
jest
|
||||
.spyOn(verifySasViyaLoginModule, 'verifySasViyaLogin')
|
||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: true }))
|
||||
jest.mock('../verifySas9Login')
|
||||
jest
|
||||
.spyOn(verifySas9LoginModule, 'verifySas9Login')
|
||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: true }))
|
||||
})
|
||||
|
||||
mockedAxios.get.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: mockLoginAuthoriseRequiredResponse
|
||||
})
|
||||
)
|
||||
it('should call the auth callback and return when already logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName
|
||||
})
|
||||
)
|
||||
|
||||
await authManager.logIn(userName, password)
|
||||
const loginResponse = await authManager.redirectedLogIn({})
|
||||
|
||||
expect(requestClient.authorize).toHaveBeenCalledWith(
|
||||
mockLoginAuthoriseRequiredResponse
|
||||
)
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should perform login via pop up if not logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: ''
|
||||
})
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName
|
||||
})
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.redirectedLogIn({})
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
||||
`/SASLogon/home`,
|
||||
'SASLogon',
|
||||
{
|
||||
width: 500,
|
||||
height: 600
|
||||
},
|
||||
undefined
|
||||
)
|
||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(2)
|
||||
expect(verifySasViyaLoginModule.verifySasViyaLogin).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should perform login via pop up if not logged in with server sas9', async () => {
|
||||
const serverType = ServerType.Sas9
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: ''
|
||||
})
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName
|
||||
})
|
||||
)
|
||||
|
||||
const loginResponse = await authManager.redirectedLogIn({})
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||
expect(loginResponse.userName).toEqual(userName)
|
||||
|
||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
||||
`/SASLogon/home`,
|
||||
'SASLogon',
|
||||
{
|
||||
width: 500,
|
||||
height: 600
|
||||
},
|
||||
undefined
|
||||
)
|
||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(2)
|
||||
expect(verifySas9LoginModule.verifySas9Login).toHaveBeenCalledTimes(1)
|
||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should return empty username if user unable to re-login via pop up', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: ''
|
||||
})
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName
|
||||
})
|
||||
)
|
||||
jest
|
||||
.spyOn(verifySasViyaLoginModule, 'verifySasViyaLogin')
|
||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: false }))
|
||||
|
||||
const loginResponse = await authManager.redirectedLogIn({})
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
||||
expect(loginResponse.userName).toEqual('')
|
||||
|
||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
||||
`/SASLogon/home`,
|
||||
'SASLogon',
|
||||
{
|
||||
width: 500,
|
||||
height: 600
|
||||
},
|
||||
undefined
|
||||
)
|
||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(authCallback).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should return empty username if user rejects to re-login', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
jest
|
||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: false,
|
||||
userName: ''
|
||||
})
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
isLoggedIn: true,
|
||||
userName
|
||||
})
|
||||
)
|
||||
jest
|
||||
.spyOn(openWebPageModule, 'openWebPage')
|
||||
.mockImplementation(() => Promise.resolve(null))
|
||||
|
||||
const loginResponse = await authManager.redirectedLogIn({})
|
||||
|
||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
||||
expect(loginResponse.userName).toEqual('')
|
||||
|
||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
||||
`/SASLogon/home`,
|
||||
'SASLogon',
|
||||
{
|
||||
width: 500,
|
||||
height: 600
|
||||
},
|
||||
undefined
|
||||
)
|
||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(authCallback).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('should check and return session information if logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
mockedAxios.get.mockImplementation(() =>
|
||||
Promise.resolve({ data: '<button onClick="logout">' })
|
||||
)
|
||||
describe('checkSession', () => {
|
||||
it('return session information when logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
mockedAxios.get.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockedCurrentUserApi(userName) })
|
||||
)
|
||||
|
||||
const response = await authManager.checkSession()
|
||||
expect(response.isLoggedIn).toBeTruthy()
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`http://test-server.com/identities`,
|
||||
{
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
transformResponse: undefined,
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
const response = await authManager.checkSession()
|
||||
expect(response.isLoggedIn).toBeTruthy()
|
||||
expect(response.userName).toEqual(userName)
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`http://test-server.com/identities/users/@currentUser`,
|
||||
{
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
transformResponse: undefined,
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
it('return session information when logged in - SAS9', async () => {
|
||||
// username cannot have `-` and cannot be uppercased
|
||||
const username = 'testusername'
|
||||
const serverType = ServerType.Sas9
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
mockedAxios.get.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: `"title":"Log Off ${username}","url":"javascript: clearFrame(\"/SASStoredProcess/do?_action=logoff\")"' })`
|
||||
})
|
||||
)
|
||||
|
||||
const response = await authManager.checkSession()
|
||||
expect(response.isLoggedIn).toBeTruthy()
|
||||
expect(response.userName).toEqual(username)
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`http://test-server.com/SASStoredProcess`,
|
||||
{
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
transformResponse: undefined,
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('return session information when logged in - SAS9 - having full name in html', async () => {
|
||||
const fullname = 'FirstName LastName'
|
||||
const username = 'firlas'
|
||||
const serverType = ServerType.Sas9
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
mockedAxios.get.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: `"title":"Log Off ${fullname}","url":"javascript: clearFrame(\"/SASStoredProcess/do?_action=logoff\")"' })`
|
||||
})
|
||||
)
|
||||
|
||||
const response = await authManager.checkSession()
|
||||
expect(response.isLoggedIn).toBeTruthy()
|
||||
expect(response.userName).toEqual(username)
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`http://test-server.com/SASStoredProcess`,
|
||||
{
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
transformResponse: undefined,
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('perform logout when not logged in', async () => {
|
||||
const authManager = new AuthManager(
|
||||
serverUrl,
|
||||
serverType,
|
||||
requestClient,
|
||||
authCallback
|
||||
)
|
||||
mockedAxios.get
|
||||
.mockImplementationOnce(() => Promise.resolve({ status: 401 }))
|
||||
.mockImplementation(() => Promise.resolve({}))
|
||||
|
||||
const response = await authManager.checkSession()
|
||||
expect(response.isLoggedIn).toBeFalsy()
|
||||
expect(response.userName).toEqual('')
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`http://test-server.com/identities/users/@currentUser`,
|
||||
{
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
transformResponse: undefined,
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
`/SASLogon/logout.do?`,
|
||||
getHeadersJson
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const getHeadersJson = {
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
responseType: 'json'
|
||||
}
|
||||
|
||||
@@ -22,3 +22,28 @@ export const generateToken = (timeToLiveSeconds: number): string => {
|
||||
const token = `${header}.${payload}.${signature}`
|
||||
return token
|
||||
}
|
||||
|
||||
export const mockedCurrentUserApi = (username: string) => ({
|
||||
creationTimeStamp: '2021-04-17T14:13:14.000Z',
|
||||
modifiedTimeStamp: '2021-08-31T22:08:07.000Z',
|
||||
id: username,
|
||||
type: 'user',
|
||||
name: 'Full User Name',
|
||||
links: [
|
||||
{
|
||||
method: 'GET',
|
||||
rel: 'self',
|
||||
href: `/identities/users/${username}`,
|
||||
uri: `/identities/users/${username}`,
|
||||
type: 'user'
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
rel: 'alternate',
|
||||
href: `/identities/users/${username}`,
|
||||
uri: `/identities/users/${username}`,
|
||||
type: 'application/vnd.sas.summary'
|
||||
}
|
||||
],
|
||||
version: 2
|
||||
})
|
||||
|
||||
64
src/auth/spec/openWebPage.spec.ts
Normal file
64
src/auth/spec/openWebPage.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { openWebPage } from '../openWebPage'
|
||||
import * as loginPromptModule from '../../utils/loginPrompt'
|
||||
|
||||
describe('openWebPage', () => {
|
||||
const serverUrl = 'http://test-server.com'
|
||||
|
||||
describe('window.open is not blocked', () => {
|
||||
const mockedOpen = jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({} as unknown as Window))
|
||||
const originalOpen = window.open
|
||||
|
||||
beforeAll(() => {
|
||||
window.open = mockedOpen
|
||||
})
|
||||
afterAll(() => {
|
||||
window.open = originalOpen
|
||||
})
|
||||
|
||||
it(`should return new Window popup - using default adapter's dialog`, async () => {
|
||||
await expect(openWebPage(serverUrl)).resolves.toBeDefined()
|
||||
|
||||
expect(mockedOpen).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('window.open is blocked', () => {
|
||||
const mockedOpen = jest.fn().mockImplementation(() => null)
|
||||
const originalOpen = window.open
|
||||
|
||||
beforeAll(() => {
|
||||
window.open = mockedOpen
|
||||
})
|
||||
afterAll(() => {
|
||||
window.open = originalOpen
|
||||
})
|
||||
|
||||
it(`should return new Window popup - using default adapter's dialog`, async () => {
|
||||
jest.mock('../../utils/loginPrompt')
|
||||
jest
|
||||
.spyOn(loginPromptModule, 'openLoginPrompt')
|
||||
.mockImplementation(() => Promise.resolve(true))
|
||||
|
||||
await expect(openWebPage(serverUrl)).resolves.toBeDefined()
|
||||
expect(loginPromptModule.openLoginPrompt).toBeCalled()
|
||||
expect(mockedOpen).toBeCalled()
|
||||
})
|
||||
|
||||
it(`should return new Window popup - using frontend's provided onloggedOut`, async () => {
|
||||
const onLoggedOut = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(true))
|
||||
|
||||
await expect(
|
||||
openWebPage(serverUrl, undefined, undefined, onLoggedOut)
|
||||
).resolves.toBeDefined()
|
||||
expect(onLoggedOut).toBeCalled()
|
||||
expect(mockedOpen).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
37
src/auth/spec/verifySas9Login.spec.ts
Normal file
37
src/auth/spec/verifySas9Login.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { verifySas9Login } from '../verifySas9Login'
|
||||
import * as delayModule from '../../utils/delay'
|
||||
|
||||
describe('verifySas9Login', () => {
|
||||
const serverUrl = 'http://test-server.com'
|
||||
|
||||
beforeAll(() => {
|
||||
jest.mock('../../utils')
|
||||
jest
|
||||
.spyOn(delayModule, 'delay')
|
||||
.mockImplementation(() => Promise.resolve({}))
|
||||
})
|
||||
|
||||
it('should return isLoggedIn true by checking state of popup', async () => {
|
||||
const popup = {
|
||||
window: {
|
||||
location: { href: serverUrl + `/SASLogon/home` },
|
||||
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
|
||||
}
|
||||
} as unknown as Window
|
||||
|
||||
await expect(verifySas9Login(popup)).resolves.toEqual({
|
||||
isLoggedIn: true
|
||||
})
|
||||
})
|
||||
|
||||
it('should return isLoggedIn false if user closed popup, already', async () => {
|
||||
const popup: Window = { closed: true } as unknown as Window
|
||||
|
||||
await expect(verifySas9Login(popup)).resolves.toEqual({
|
||||
isLoggedIn: false
|
||||
})
|
||||
})
|
||||
})
|
||||
38
src/auth/spec/verifySasViyaLogin.spec.ts
Normal file
38
src/auth/spec/verifySasViyaLogin.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { verifySasViyaLogin } from '../verifySasViyaLogin'
|
||||
import * as delayModule from '../../utils/delay'
|
||||
|
||||
describe('verifySasViyaLogin', () => {
|
||||
const serverUrl = 'http://test-server.com'
|
||||
|
||||
beforeAll(() => {
|
||||
jest.mock('../../utils')
|
||||
jest
|
||||
.spyOn(delayModule, 'delay')
|
||||
.mockImplementation(() => Promise.resolve({}))
|
||||
document.cookie = encodeURIComponent('Current-User={"userId":"user-hash"}')
|
||||
})
|
||||
|
||||
it('should return isLoggedIn true by checking state of popup', async () => {
|
||||
const popup = {
|
||||
window: {
|
||||
location: { href: serverUrl + `/SASLogon/home` },
|
||||
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
|
||||
}
|
||||
} as unknown as Window
|
||||
|
||||
await expect(verifySasViyaLogin(popup)).resolves.toEqual({
|
||||
isLoggedIn: true
|
||||
})
|
||||
})
|
||||
|
||||
it('should return isLoggedIn false if user closed popup, already', async () => {
|
||||
const popup: Window = { closed: true } as unknown as Window
|
||||
|
||||
await expect(verifySasViyaLogin(popup)).resolves.toEqual({
|
||||
isLoggedIn: false
|
||||
})
|
||||
})
|
||||
})
|
||||
20
src/auth/verifySas9Login.ts
Normal file
20
src/auth/verifySas9Login.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { delay } from '../utils'
|
||||
|
||||
export async function verifySas9Login(loginPopup: Window): Promise<{
|
||||
isLoggedIn: boolean
|
||||
}> {
|
||||
let isLoggedIn = false
|
||||
let startTime = new Date()
|
||||
let elapsedSeconds = 0
|
||||
do {
|
||||
await delay(1000)
|
||||
if (loginPopup.closed) break
|
||||
|
||||
isLoggedIn =
|
||||
loginPopup.window.location.href.includes('SASLogon') &&
|
||||
loginPopup.window.document.body.innerText.includes('You have signed in.')
|
||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
||||
} while (!isLoggedIn && elapsedSeconds < 5 * 60)
|
||||
|
||||
return { isLoggedIn }
|
||||
}
|
||||
33
src/auth/verifySasViyaLogin.ts
Normal file
33
src/auth/verifySasViyaLogin.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { delay } from '../utils'
|
||||
|
||||
export async function verifySasViyaLogin(loginPopup: Window): Promise<{
|
||||
isLoggedIn: boolean
|
||||
}> {
|
||||
let isLoggedIn = false
|
||||
let startTime = new Date()
|
||||
let elapsedSeconds = 0
|
||||
do {
|
||||
await delay(1000)
|
||||
if (loginPopup.closed) break
|
||||
isLoggedIn = isLoggedInSASVIYA()
|
||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
||||
} while (!isLoggedIn && elapsedSeconds < 5 * 60)
|
||||
|
||||
let isAuthorized = false
|
||||
startTime = new Date()
|
||||
do {
|
||||
await delay(1000)
|
||||
if (loginPopup.closed) break
|
||||
isAuthorized =
|
||||
loginPopup.window.location.href.includes('SASLogon') ||
|
||||
loginPopup.window.document.body?.innerText?.includes(
|
||||
'You have signed in.'
|
||||
)
|
||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
||||
} while (!isAuthorized && elapsedSeconds < 5 * 60)
|
||||
|
||||
return { isLoggedIn: isLoggedIn && isAuthorized }
|
||||
}
|
||||
|
||||
export const isLoggedInSASVIYA = () =>
|
||||
document.cookie.includes('Current-User') && document.cookie.includes('userId')
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../types/errors'
|
||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { appendExtraResponseAttributes } from '../utils'
|
||||
|
||||
export class JesJobExecutor extends BaseJobExecutor {
|
||||
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
||||
@@ -29,21 +30,10 @@ export class JesJobExecutor extends BaseJobExecutor {
|
||||
.then((response: any) => {
|
||||
this.appendRequest(response, sasJob, config.debug)
|
||||
|
||||
let responseObject = {}
|
||||
|
||||
if (extraResponseAttributes && extraResponseAttributes.length > 0) {
|
||||
const extraAttributes = extraResponseAttributes.reduce(
|
||||
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
||||
{}
|
||||
)
|
||||
|
||||
responseObject = {
|
||||
result: response.result,
|
||||
...extraAttributes
|
||||
}
|
||||
} else {
|
||||
responseObject = response.result
|
||||
}
|
||||
const responseObject = appendExtraResponseAttributes(
|
||||
response,
|
||||
extraResponseAttributes
|
||||
)
|
||||
|
||||
resolve(responseObject)
|
||||
})
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import {
|
||||
AuthConfig,
|
||||
ExtraResponseAttributes,
|
||||
ServerType
|
||||
} from '@sasjs/utils/types'
|
||||
import {
|
||||
ErrorResponse,
|
||||
JobExecutionError,
|
||||
LoginRequiredError,
|
||||
WeboutResponseError
|
||||
LoginRequiredError
|
||||
} from '../types/errors'
|
||||
import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
||||
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
||||
@@ -11,8 +14,8 @@ import { RequestClient } from '../request/RequestClient'
|
||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||
import {
|
||||
isRelativePath,
|
||||
getValidJson,
|
||||
parseSasViyaDebugResponse
|
||||
parseSasViyaDebugResponse,
|
||||
appendExtraResponseAttributes
|
||||
} from '../utils'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
@@ -37,7 +40,9 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
sasJob: string,
|
||||
data: any,
|
||||
config: any,
|
||||
loginRequiredCallback?: any
|
||||
loginRequiredCallback?: any,
|
||||
authConfig?: AuthConfig,
|
||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
||||
) {
|
||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||
const program = isRelativePath(sasJob)
|
||||
@@ -48,10 +53,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
||||
|
||||
if (config.serverType === ServerType.SasViya) {
|
||||
const jobUri =
|
||||
config.serverType === ServerType.SasViya
|
||||
? await this.getJobUri(sasJob)
|
||||
: ''
|
||||
const jobUri = await this.getJobUri(sasJob)
|
||||
|
||||
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
||||
|
||||
@@ -113,27 +115,33 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient!.post(apiUrl, formData, undefined)
|
||||
.then(async (res: any) => {
|
||||
if (this.serverType === ServerType.SasViya && config.debug) {
|
||||
const jsonResponse = await parseSasViyaDebugResponse(
|
||||
res.result,
|
||||
this.requestClient,
|
||||
this.serverUrl
|
||||
)
|
||||
this.appendRequest(res, sasJob, config.debug)
|
||||
resolve(jsonResponse)
|
||||
}
|
||||
if (this.serverType === ServerType.Sas9 && config.debug) {
|
||||
let jsonResponse = res.result
|
||||
if (typeof res.result === 'string')
|
||||
jsonResponse = parseWeboutResponse(res.result, apiUrl)
|
||||
let jsonResponse = res.result
|
||||
|
||||
getValidJson(jsonResponse)
|
||||
this.appendRequest(res, sasJob, config.debug)
|
||||
resolve(res.result)
|
||||
if (config.debug) {
|
||||
switch (this.serverType) {
|
||||
case ServerType.SasViya:
|
||||
jsonResponse = await parseSasViyaDebugResponse(
|
||||
res.result,
|
||||
this.requestClient,
|
||||
this.serverUrl
|
||||
)
|
||||
break
|
||||
case ServerType.Sas9:
|
||||
jsonResponse =
|
||||
typeof res.result === 'string'
|
||||
? parseWeboutResponse(res.result, apiUrl)
|
||||
: res.result
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.appendRequest(res, sasJob, config.debug)
|
||||
getValidJson(res.result as string)
|
||||
resolve(res.result)
|
||||
|
||||
const responseObject = appendExtraResponseAttributes(
|
||||
{ result: jsonResponse },
|
||||
extraResponseAttributes
|
||||
)
|
||||
resolve(responseObject)
|
||||
})
|
||||
.catch(async (e: Error) => {
|
||||
if (e instanceof JobExecutionError) {
|
||||
@@ -143,14 +151,14 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
|
||||
if (e instanceof LoginRequiredError) {
|
||||
await loginCallback()
|
||||
|
||||
this.appendWaitingRequest(() => {
|
||||
return this.execute(
|
||||
sasJob,
|
||||
data,
|
||||
config,
|
||||
loginRequiredCallback
|
||||
loginRequiredCallback,
|
||||
authConfig,
|
||||
extraResponseAttributes
|
||||
).then(
|
||||
(res: any) => {
|
||||
resolve(res)
|
||||
@@ -160,6 +168,8 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await loginCallback()
|
||||
} else {
|
||||
reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
|
||||
@@ -499,46 +499,60 @@ export const throwIfError = (response: AxiosResponse) => {
|
||||
}
|
||||
|
||||
const parseError = (data: string) => {
|
||||
if (!data) return null
|
||||
|
||||
try {
|
||||
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
||||
return responseJson.errorCode && responseJson.message
|
||||
? new JobExecutionError(
|
||||
responseJson.errorCode,
|
||||
responseJson.message,
|
||||
data?.replace(/[\n\r]/g, ' ')
|
||||
)
|
||||
: null
|
||||
} catch (_) {
|
||||
try {
|
||||
const hasError = data?.includes('{"errorCode')
|
||||
if (hasError) {
|
||||
const parts = data.split('{"errorCode')
|
||||
if (parts.length > 1) {
|
||||
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
||||
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
||||
return new JobExecutionError(
|
||||
errorJson.errorCode,
|
||||
errorJson.message,
|
||||
data?.replace(/[\n\r]/g, '\n')
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const hasError = !!data?.match(/stored process not found: /i)
|
||||
if (hasError) {
|
||||
const parts = data.split(/stored process not found: /i)
|
||||
if (parts.length > 1) {
|
||||
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
||||
const message = `Stored process not found: ${storedProcessPath}`
|
||||
return new JobExecutionError(404, message, '')
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
} catch (_) {
|
||||
return null
|
||||
if (responseJson.errorCode && responseJson.message) {
|
||||
return new JobExecutionError(
|
||||
responseJson.errorCode,
|
||||
responseJson.message,
|
||||
data?.replace(/[\n\r]/g, ' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
const hasError = data?.includes('{"errorCode')
|
||||
if (hasError) {
|
||||
const parts = data.split('{"errorCode')
|
||||
if (parts.length > 1) {
|
||||
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
||||
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
||||
return new JobExecutionError(
|
||||
errorJson.errorCode,
|
||||
errorJson.message,
|
||||
data?.replace(/[\n\r]/g, '\n')
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
const hasError = !!data?.match(/stored process not found: /i)
|
||||
if (hasError) {
|
||||
const parts = data.split(/stored process not found: /i)
|
||||
if (parts.length > 1) {
|
||||
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
||||
const message = `Stored process not found: ${storedProcessPath}`
|
||||
return new JobExecutionError(404, message, '')
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
const hasError =
|
||||
!!data?.match(/Stored Process Error/i) &&
|
||||
!!data?.match(/This request completed with errors./i)
|
||||
if (hasError) {
|
||||
const parts = data.split('<h2>SAS Log</h2>')
|
||||
if (parts.length > 1) {
|
||||
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
|
||||
const message = `This request completed with errors.`
|
||||
return new JobExecutionError(404, message, log)
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
8
src/types/Login.ts
Normal file
8
src/types/Login.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface LoginOptions {
|
||||
onLoggedOut?: () => Promise<boolean>
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
isLoggedIn: boolean
|
||||
userName: string
|
||||
}
|
||||
@@ -59,4 +59,13 @@ export class SASjsConfig {
|
||||
* Changing this setting is not recommended.
|
||||
*/
|
||||
allowInsecureRequests = false
|
||||
/**
|
||||
* Supported login mechanisms are - Redirected and Default
|
||||
*/
|
||||
loginMechanism: LoginMechanism = LoginMechanism.Default
|
||||
}
|
||||
|
||||
export enum LoginMechanism {
|
||||
Default = 'Default',
|
||||
Redirected = 'Redirected'
|
||||
}
|
||||
|
||||
22
src/utils/appendExtraResponseAttributes.ts
Normal file
22
src/utils/appendExtraResponseAttributes.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||
|
||||
export async function appendExtraResponseAttributes(
|
||||
response: any,
|
||||
extraResponseAttributes: ExtraResponseAttributes[]
|
||||
) {
|
||||
let responseObject = {}
|
||||
|
||||
if (extraResponseAttributes?.length) {
|
||||
const extraAttributes = extraResponseAttributes.reduce(
|
||||
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
||||
{}
|
||||
)
|
||||
|
||||
responseObject = {
|
||||
result: response.result,
|
||||
...extraAttributes
|
||||
}
|
||||
} else responseObject = response.result
|
||||
|
||||
return responseObject
|
||||
}
|
||||
2
src/utils/delay.ts
Normal file
2
src/utils/delay.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const delay = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './asyncForEach'
|
||||
export * from './compareTimestamps'
|
||||
export * from './convertToCsv'
|
||||
export * from './delay'
|
||||
export * from './isNode'
|
||||
export * from './isRelativePath'
|
||||
export * from './isUri'
|
||||
@@ -15,3 +16,4 @@ export * from './parseWeboutResponse'
|
||||
export * from './fetchLogByChunks'
|
||||
export * from './getValidJson'
|
||||
export * from './parseViyaDebugResponse'
|
||||
export * from './appendExtraResponseAttributes'
|
||||
|
||||
167
src/utils/loginPrompt/index.ts
Normal file
167
src/utils/loginPrompt/index.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
enum domIDs {
|
||||
styles = 'sasjsAdapterStyles',
|
||||
overlay = 'sasjsAdapterLoginPromptBG',
|
||||
dialog = 'sasjsAdapterLoginPrompt'
|
||||
}
|
||||
|
||||
export const openLoginPrompt = (): Promise<boolean> => {
|
||||
return new Promise(async (resolve) => {
|
||||
const style = document.createElement('style')
|
||||
style.id = domIDs.styles
|
||||
style.innerText = cssContent
|
||||
|
||||
const loginPromptBG = document.createElement('div')
|
||||
loginPromptBG.id = domIDs.overlay
|
||||
loginPromptBG.classList.add('popUpBG')
|
||||
|
||||
const loginPrompt = document.createElement('div')
|
||||
loginPrompt.id = domIDs.dialog
|
||||
loginPrompt.classList.add('popUp')
|
||||
|
||||
const title = document.createElement('h1')
|
||||
title.innerText = 'Session Expired!'
|
||||
loginPrompt.appendChild(title)
|
||||
|
||||
const descHolder = document.createElement('div')
|
||||
const desc = document.createElement('span')
|
||||
desc.innerText = 'You need to relogin, click OK to login.'
|
||||
descHolder.appendChild(desc)
|
||||
loginPrompt.appendChild(descHolder)
|
||||
|
||||
const buttonCancel = document.createElement('button')
|
||||
buttonCancel.classList.add('cancel')
|
||||
buttonCancel.innerText = 'Cancel'
|
||||
buttonCancel.onclick = () => {
|
||||
closeLoginPrompt()
|
||||
resolve(false)
|
||||
}
|
||||
loginPrompt.appendChild(buttonCancel)
|
||||
|
||||
const buttonOk = document.createElement('button')
|
||||
buttonOk.classList.add('confirm')
|
||||
buttonOk.innerText = 'Ok'
|
||||
buttonOk.onclick = () => {
|
||||
closeLoginPrompt()
|
||||
resolve(true)
|
||||
}
|
||||
loginPrompt.appendChild(buttonOk)
|
||||
|
||||
document.body.style.overflow = 'hidden'
|
||||
|
||||
document.body.appendChild(style)
|
||||
document.body.appendChild(loginPromptBG)
|
||||
document.body.appendChild(loginPrompt)
|
||||
})
|
||||
}
|
||||
const closeLoginPrompt = () => {
|
||||
Object.values(domIDs).forEach((id) => {
|
||||
const elem = document.getElementById(id)
|
||||
elem?.parentNode?.removeChild(elem)
|
||||
})
|
||||
|
||||
document.body.style.overflow = 'auto'
|
||||
}
|
||||
|
||||
const cssContent = `
|
||||
.popUp {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
color: #fff;
|
||||
border-style: none;
|
||||
z-index: 999;
|
||||
overflow: hidden;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.popUp > h1 {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
padding: 5px;
|
||||
min-height: 40px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
border-width: 5px;
|
||||
border-color: black;
|
||||
}
|
||||
.popUp > div {
|
||||
width: 100%;
|
||||
height: calc(100% -108px);
|
||||
margin: 0;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
padding: 5%;
|
||||
text-align: center;
|
||||
border-width: 1px;
|
||||
border-color: #ccc;
|
||||
border-style: none none solid none;
|
||||
overflow: auto;
|
||||
}
|
||||
.popUp > div > span {
|
||||
display: table-cell;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 300px;
|
||||
height: 108px;
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
.popUp .cancel {
|
||||
float: left;
|
||||
}
|
||||
.popUp .confirm {
|
||||
float: right;
|
||||
}
|
||||
.popUp > button {
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
border: 1px none #ccc;
|
||||
color: #fff;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
height: 50px;
|
||||
background: rgba(1, 1, 1, 0.2);
|
||||
}
|
||||
.popUp > button:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.popUpBG {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
opacity: 0.95;
|
||||
z-index: 50;
|
||||
background-image: radial-gradient(#0378cd, #012036);
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user