1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-03 10:40:06 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Allan Bowe
0f20048fb4 Merge pull request #838 from sasjs/bumputils
fix: bumping sasjs/utils to 3.5
2025-02-27 09:30:55 +00:00
249837dacf ci: run on older ubuntu 2025-02-26 16:48:17 +01:00
a115c12f55 fix(ci): vpn files 2025-02-26 16:41:48 +01:00
61c4d21467 fix(ci): new vpn config 2025-02-26 16:28:48 +01:00
3e9f38529f ci: validate VPN connection 2025-02-26 16:15:55 +01:00
Allan Bowe
06f79307b9 chore: logging info 2025-02-26 15:11:52 +00:00
Allan Bowe
5122d2a9c9 fix: matrix bot address 2025-02-26 14:56:43 +00:00
mulahasanovic
dc3eb3f0db chore(git): merge branch 'bumputils' of github.com:sasjs/adapter into bumputils 2025-02-26 15:42:52 +01:00
mulahasanovic
b940bc7cc3 fix(tests): special character case 2025-02-26 15:36:43 +01:00
Allan Bowe
82fc55ac1c chore: adding more logging to workflow 2025-02-26 14:03:32 +00:00
fc1a22c8c5 chore(git): merge 2025-02-26 11:20:58 +01:00
57b9f86077 fix(ci): openvpn hangs 2025-02-26 11:19:19 +01:00
Allan Bowe
68f7b2eac2 fix: updating pkg lock 2025-02-26 10:13:20 +00:00
Allan Bowe
2676873bb0 fix: bump utils to 3.5.1 2025-02-26 10:09:39 +00:00
add2f0a860 fix(ci): openvpn 2025-02-26 10:05:57 +01:00
2072136577 fix: new node, externals 2025-02-26 09:53:43 +01:00
afae632fc6 fix: bump rimraf (versions prior to v4 are deprecated) 2025-02-20 14:28:27 +01:00
Allan Bowe
317587a3c8 fix: bumping sasjs/utils to 3.5 2025-02-15 14:32:50 +00:00
Allan Bowe
ffd6bc5a5c Merge pull request #836 from sasjs/issue-835
feat(auth): added multi-language support to logIn method
2024-06-21 13:40:20 +01:00
Yury
c2e64d9ba6 chore: cleanup 2024-06-21 11:32:58 +03:00
Yury
a90f699abd feat(auth): added utils to get and check login header 2024-06-21 11:24:06 +03:00
Yury
2cca192f88 chore(auth-manager): added comment 2024-06-20 17:38:56 +03:00
Yury
053b07769a feat(auth): added multi-language support to logIn method 2024-06-20 17:15:05 +03:00
Yury Shkoda
4c4511913c Merge pull request #834 from sasjs/redirectLogin-fix
fix(auth-manager): fixed redirectedLoginUrl
2024-02-19 15:59:04 +03:00
Yury
8c64c24f3c chore: left a comment 2024-02-19 15:26:26 +03:00
Yury
1f2f445002 chore: fixed SASLogon URL in AuthManager test 2024-02-19 11:06:09 +03:00
Yury
6afa056a86 chore: testing 2024-02-19 10:58:23 +03:00
Yury
fe47ca1152 fix(auth-manager): fixed redirectedLoginUrl 2024-02-19 08:50:19 +03:00
Yury Shkoda
10da691f0f Merge pull request #833 from sasjs/parent-session-state-check
fix(poll-job-state): fixed checking session state
2023-09-15 12:12:24 +03:00
Yury Shkoda
318f9694cb test: updated unit tests threshold 2023-09-15 08:58:46 +03:00
Yury Shkoda
56e6131e5c fix(poll-job-state): fixed checking session state 2023-09-15 08:51:39 +03:00
23 changed files with 4157 additions and 16819 deletions

View File

@@ -3,10 +3,12 @@ client
tls-client
dev tun
# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934)
proto tcp
remote vpn.4gl.io 7494
proto udp
remote vpn.4gl.io 7194
resolv-retry infinite
cipher AES-256-CBC
# this will fallback from udp6 to udp4 as well
connect-timeout 5
data-ciphers AES-256-CBC:AES-256-GCM
auth SHA256
script-security 2
keepalive 10 120

View File

@@ -8,7 +8,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
matrix:
@@ -22,8 +22,9 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Check npm audit
run: npm audit --production --audit-level=low
# FIXME: uncomment 'Check npm audit' step after axios version bump
# - name: Check npm audit
# run: npm audit --production --audit-level=low
- name: Install Dependencies
run: npm ci
@@ -52,6 +53,10 @@ jobs:
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Chmod VPN files
run: |
chmod 600 .github/vpn/ca.crt .github/vpn/user.crt .github/vpn/user.key .github/vpn/tls.key
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
@@ -67,6 +72,9 @@ jobs:
- name: install pm2
run: npm i -g pm2
- name: Fetch SASJS server
run: curl ${{ secrets.SASJS_SERVER_URL }}/SASjsApi/info
- name: Deploy sasjs-tests
run: |
npm install -g replace-in-files-cli
@@ -75,8 +83,10 @@ jobs:
npm i
replace-in-files --regex='"serverUrl".*' --replacement='"serverUrl":"${{ secrets.SASJS_SERVER_URL }}",' ./public/config.json
replace-in-files --regex='"userName".*' --replacement='"userName":"${{ secrets.SASJS_USERNAME }}",' ./public/config.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json
replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json
cat ./public/config.json
npm run update:adapter
pm2 start --name sasjs-test npm -- start
@@ -89,6 +99,9 @@ jobs:
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"http://localhost:3000",' ./cypress.json
replace-in-files --regex='"username".*' --replacement='"username":"${{ secrets.SASJS_USERNAME }}",' ./cypress.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./cypress.json
cat ./cypress.json
echo "SASJS_USERNAME=${{ secrets.SASJS_USERNAME }}"
sh ./sasjs-tests/sasjs-cypress-run.sh ${{ secrets.MATRIX_TOKEN }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
# For some reason if coverage report action is run before other commands, those commands can't access the directories and files on which they depend on

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"cSpell.words": ["SASVIYA"]
}

View File

@@ -43,10 +43,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
statements: 64.01,
statements: 64.03,
branches: 45.11,
functions: 54.1,
lines: 64.51
functions: 54.18,
lines: 64.53
}
},

20633
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@
"pem": "1.14.5",
"prettier": "2.8.7",
"process": "0.11.10",
"rimraf": "3.0.2",
"rimraf": "^6.0.1",
"semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.6",
"ts-jest": "27.1.3",
@@ -77,7 +77,7 @@
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "2.52.0",
"@sasjs/utils": "^3.5.1",
"axios": "0.27.2",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",

View File

@@ -4,7 +4,7 @@ if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
echo '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}'
curl -XPOST -d '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}' https://matrix.4gl.io/_matrix/client/r0/rooms/%21jRebyiGmHZlpfDwYXN:4gl.io:4gl.io/send/m.room.message?access_token=$1
curl -XPOST -d '{"msgtype":"m.text", "body":"Automated sasjs-tests failed on the @sasjs/adapter PR: '$2'"}' https://matrix.4gl.io/_matrix/client/r0/rooms/%21jRebyiGmHZlpfDwYXN:4gl.io/send/m.room.message?access_token=$1
echo "Cypress sasjs testing failed!"
exit 1
fi

View File

@@ -134,6 +134,9 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
return adapter.request('common/sendArr', moreSpecialCharData)
},
assertion: (res: any) => {
// If sas session is latin9 we can't process the special characters
if (res.SYSENCODING === 'latin9') return true
return (
res.table1[0][0] === moreSpecialCharData.table1[0].speech0 &&
res.table1[0][1] === moreSpecialCharData.table1[0].pct &&

View File

@@ -223,7 +223,7 @@ const needsRetry = (state: string) =>
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
* @param streamLog - indicates if job log should be streamed.
* @param logStream - job log stream.
* @param jobSessionManager - job session object containing session object and an instance of Session Manager. Job session object is used to periodically (every 10th job state poll) check parent session state.
* @param jobSessionManager - job session object containing session object and an instance of Session Manager. Job session object is used to periodically (every 10th job state poll) check parent session state. Session state is considered healthy if it is equal to 'running' or 'idle'.
* @returns - a promise which resolves with a job state
*/
export const doPoll = async (
@@ -263,15 +263,20 @@ export const doPoll = async (
throw new JobStatePollError(jobId, err)
})
// Checks if session state is equal to 'running' or 'idle'.
const isSessionStatesHealthy = (state: string) =>
[SessionState.Running, SessionState.Idle].includes(
state as SessionState
)
// Clear parent session and throw an error if session state is not
// 'running' or response status is not 200.
if (sessionState !== SessionState.Running || responseStatus !== 200) {
// 'running', 'idle' or response status is not 200.
if (!isSessionStatesHealthy(sessionState) || responseStatus !== 200) {
sessionManager.clearSession(sessionId, access_token)
const sessionError =
sessionState !== SessionState.Running
? `Session state of the job is not 'running'. Session state is '${sessionState}'`
: `Session response status is not 200. Session response status is ${responseStatus}.`
const sessionError = isSessionStatesHealthy(sessionState)
? `Session response status is not 200. Session response status is ${responseStatus}.`
: `Session state of the job is not 'running' or 'idle'. Session state is '${sessionState}'`
throw new JobStatePollError(jobId, new Error(sessionError))
}

View File

@@ -465,7 +465,7 @@ describe('doPoll', () => {
.spyOn(sessionManager as any, 'getSessionState')
.mockImplementation(() => {
return Promise.resolve({
result: SessionState.Running,
result: SessionState.Idle,
responseStatus: 200
})
})
@@ -533,9 +533,9 @@ describe('doPoll', () => {
)
})
it('should throw an error if session is not in running state', async () => {
it('should throw an error if session state is not healthy', async () => {
const filteredSessionStates = Object.values(SessionState).filter(
(state) => state !== SessionState.Running
(state) => state !== SessionState.Running && state !== SessionState.Idle
)
const randomSessionState =
filteredSessionStates[
@@ -580,7 +580,7 @@ describe('doPoll', () => {
new JobStatePollError(
mockJob.id,
new Error(
`Session state of the job is not 'running'. Session state is '${randomSessionState}'`
`Session state of the job is not 'running' or 'idle'. Session state is '${randomSessionState}'`
)
)
)

View File

@@ -7,6 +7,7 @@ import { extractUserLongNameSas9 } from '../utils/sas9/extractUserLongNameSas9'
import { openWebPage } from './openWebPage'
import { verifySas9Login } from './verifySas9Login'
import { verifySasViyaLogin } from './verifySasViyaLogin'
import { isLogInSuccessHeaderPresent } from './'
export class AuthManager {
public userName = ''
@@ -14,6 +15,7 @@ export class AuthManager {
private loginUrl: string
private logoutUrl: string
private redirectedLoginUrl = `/SASLogon` //SAS 9 M8 no longer redirects from `/SASLogon/home` to the login page. `/SASLogon` seems to be stable enough across SAS versions
constructor(
private serverUrl: string,
private serverType: ServerType,
@@ -27,6 +29,8 @@ export class AuthManager {
: this.serverType === ServerType.SasViya
? '/SASLogon/logout.do?'
: '/SASLogon/logout'
this.redirectedLoginUrl = this.serverUrl + this.redirectedLoginUrl
}
/**
@@ -129,7 +133,7 @@ export class AuthManager {
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
let isLoggedIn = isLogInSuccess(this.serverType, loginResponse)
let isLoggedIn = isLogInSuccessHeaderPresent(this.serverType, loginResponse)
if (!isLoggedIn) {
if (isCredentialsVerifyError(loginResponse)) {
@@ -214,7 +218,7 @@ export class AuthManager {
* - a boolean `isLoggedIn`
* - a string `userName`,
* - a string `userFullName` and
* - a form `loginForm` if not loggedin.
* - a form `loginForm` if not loggedIn.
*/
public async checkSession(): Promise<LoginResultInternal> {
const { isLoggedIn, userName, userLongName } = await this.fetchUserName()
@@ -381,9 +385,3 @@ const isCredentialsVerifyError = (response: string): boolean =>
/An error occurred while the system was verifying your credentials. Please enter your credentials again./gm.test(
response
)
const isLogInSuccess = (serverType: ServerType, response: any): boolean => {
if (serverType === ServerType.Sasjs) return response?.loggedin
return /You have signed in/gm.test(response)
}

View File

@@ -1,3 +1,4 @@
export * from './AuthManager'
export * from './isAuthorizeFormRequired'
export * from './isLoginRequired'
export * from './loginHeader'

97
src/auth/loginHeader.ts Normal file
View File

@@ -0,0 +1,97 @@
import { ServerType } from '@sasjs/utils/types'
import { getUserLanguage } from '../utils'
const enLoginSuccessHeader = 'You have signed in.'
export const defaultSuccessHeaderKey = 'default'
// The following headers provided by https://github.com/sasjs/adapter/issues/835#issuecomment-2177818601
export const loginSuccessHeaders: { [key: string]: string } = {
es: `Ya se ha iniciado la sesi\u00f3n.`,
th: `\u0e04\u0e38\u0e13\u0e25\u0e07\u0e0a\u0e37\u0e48\u0e2d\u0e40\u0e02\u0e49\u0e32\u0e43\u0e0a\u0e49\u0e41\u0e25\u0e49\u0e27`,
ja: `\u30b5\u30a4\u30f3\u30a4\u30f3\u3057\u307e\u3057\u305f\u3002`,
nb: `Du har logget deg p\u00e5.`,
sl: `Prijavili ste se.`,
ar: `\u0644\u0642\u062f \u0642\u0645\u062a `,
sk: `Prihl\u00e1sili ste sa.`,
zh_HK: `\u60a8\u5df2\u767b\u5165\u3002`,
zh_CN: `\u60a8\u5df2\u767b\u5f55\u3002`,
it: `L'utente si \u00e8 connesso.`,
sv: `Du har loggat in.`,
he: `\u05e0\u05db\u05e0\u05e1\u05ea `,
nl: `U hebt zich aangemeld.`,
pl: `Zosta\u0142e\u015b zalogowany.`,
ko: `\ub85c\uadf8\uc778\ud588\uc2b5\ub2c8\ub2e4.`,
zh_TW: `\u60a8\u5df2\u767b\u5165\u3002`,
tr: `Oturum a\u00e7t\u0131n\u0131z.`,
iw: `\u05e0\u05db\u05e0\u05e1\u05ea `,
fr: `Vous \u00eates connect\u00e9.`,
uk: `\u0412\u0438 \u0432\u0432\u0456\u0439\u0448\u043b\u0438 \u0432 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441.`,
pt_BR: `Voc\u00ea se conectou.`,
no: `Du har logget deg p\u00e5.`,
cs: `Jste p\u0159ihl\u00e1\u0161eni.`,
fi: `Olet kirjautunut sis\u00e4\u00e4n.`,
ru: `\u0412\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043b\u0438 \u0432\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443.`,
el: `\u0388\u03c7\u03b5\u03c4\u03b5 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af.`,
hr: `Prijavili ste se.`,
da: `Du er logget p\u00e5.`,
de: `Sie sind jetzt angemeldet.`,
sh: `Prijavljeni ste.`,
pt: `Iniciou sess\u00e3o.`,
hu: `Bejelentkezett.`,
sr: `Prijavljeni ste.`,
en: enLoginSuccessHeader,
[defaultSuccessHeaderKey]: enLoginSuccessHeader
}
/**
* Provides expected login header based on language settings of the browser.
* @returns - expected header as a string.
*/
export const getExpectedLogInSuccessHeader = (): string => {
// get default success header
let successHeader = loginSuccessHeaders[defaultSuccessHeaderKey]
// get user language based on language settings of the browser
const userLang = getUserLanguage()
if (userLang) {
// get success header on exact match of the language code
let userLangSuccessHeader = loginSuccessHeaders[userLang]
// handle case when there is no exact match of the language code
if (!userLangSuccessHeader) {
// get all supported language codes
const headerLanguages = Object.keys(loginSuccessHeaders)
// find language code on partial match
const headerLanguage = headerLanguages.find((language) =>
new RegExp(language, 'i').test(userLang)
)
// reassign success header if partial match was found
if (headerLanguage) {
successHeader = loginSuccessHeaders[headerLanguage]
}
} else {
successHeader = userLangSuccessHeader
}
}
return successHeader
}
/**
* Checks if Login success header is present in the response based on language settings of the browser.
* @param serverType - server type.
* @param response - response object.
* @returns - boolean indicating if Login success header is present.
*/
export const isLogInSuccessHeaderPresent = (
serverType: ServerType,
response: any
): boolean => {
if (serverType === ServerType.Sasjs) return response?.loggedIn
return new RegExp(getExpectedLogInSuccessHeader(), 'gm').test(response)
}

View File

@@ -1,17 +1,22 @@
/**
* @jest-environment jsdom
*/
import { AuthManager } from '../AuthManager'
import * as dotenv from 'dotenv'
import { ServerType } from '@sasjs/utils/types'
import axios from 'axios'
import {
mockedCurrentUserApi,
mockLoginAuthoriseRequiredResponse,
mockLoginSuccessResponse
mockLoginAuthoriseRequiredResponse
} 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'
import { getExpectedLogInSuccessHeader } from '../'
jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios>
@@ -125,6 +130,7 @@ describe('AuthManager', () => {
requestClient,
authCallback
)
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({
isLoggedIn: false,
@@ -133,8 +139,9 @@ describe('AuthManager', () => {
loginForm: { name: 'test' }
})
)
mockedAxios.post.mockImplementation(() =>
Promise.resolve({ data: mockLoginSuccessResponse })
Promise.resolve({ data: getExpectedLogInSuccessHeader() })
)
const loginResponse = await authManager.logIn(userName, password)
@@ -170,6 +177,7 @@ describe('AuthManager', () => {
requestClient,
authCallback
)
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({
isLoggedIn: false,
@@ -178,8 +186,9 @@ describe('AuthManager', () => {
loginForm: { name: 'test' }
})
)
mockedAxios.post.mockImplementation(() =>
Promise.resolve({ data: mockLoginSuccessResponse })
Promise.resolve({ data: getExpectedLogInSuccessHeader() })
)
mockedAxios.get.mockImplementation(() => Promise.resolve({ status: 200 }))
@@ -365,7 +374,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual(userName)
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon`,
`${serverUrl}/SASLogon`,
'SASLogon',
{
width: 500,
@@ -409,7 +418,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual(userName)
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon`,
`${serverUrl}/SASLogon`,
'SASLogon',
{
width: 500,
@@ -453,7 +462,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual('')
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon`,
`${serverUrl}/SASLogon`,
'SASLogon',
{
width: 500,
@@ -497,7 +506,7 @@ describe('AuthManager', () => {
expect(loginResponse.userName).toEqual('')
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
`/SASLogon`,
`${serverUrl}/SASLogon`,
'SASLogon',
{
width: 500,

View File

@@ -0,0 +1,82 @@
/**
* @jest-environment jsdom
*/
import { ServerType } from '@sasjs/utils/types'
import {
loginSuccessHeaders,
isLogInSuccessHeaderPresent,
defaultSuccessHeaderKey
} from '../'
describe('isLogInSuccessHeaderPresent', () => {
let languageGetter: any
beforeEach(() => {
languageGetter = jest.spyOn(window.navigator, 'language', 'get')
})
it('should check SASVIYA and SAS9 login success header based on language preferences of the browser', () => {
// test SASVIYA server type
Object.keys(loginSuccessHeaders).forEach((key) => {
languageGetter.mockReturnValue(key)
expect(
isLogInSuccessHeaderPresent(
ServerType.SasViya,
loginSuccessHeaders[key]
)
).toBeTruthy()
})
// test SAS9 server type
Object.keys(loginSuccessHeaders).forEach((key) => {
languageGetter.mockReturnValue(key)
expect(
isLogInSuccessHeaderPresent(ServerType.Sas9, loginSuccessHeaders[key])
).toBeTruthy()
})
// test possible longer language codes
const possibleLanguageCodes = [
{ short: 'en', long: 'en-US' },
{ short: 'fr', long: 'fr-FR' },
{ short: 'es', long: 'es-ES' }
]
possibleLanguageCodes.forEach((key) => {
const { short, long } = key
languageGetter.mockReturnValue(long)
expect(
isLogInSuccessHeaderPresent(
ServerType.SasViya,
loginSuccessHeaders[short]
)
).toBeTruthy()
})
// test falling back to default language code
languageGetter.mockReturnValue('WRONG-LANGUAGE')
expect(
isLogInSuccessHeaderPresent(
ServerType.Sas9,
loginSuccessHeaders[defaultSuccessHeaderKey]
)
).toBeTruthy()
})
it('should check SASVJS login success header', () => {
expect(
isLogInSuccessHeaderPresent(ServerType.Sasjs, { loggedIn: true })
).toBeTruthy()
expect(
isLogInSuccessHeaderPresent(ServerType.Sasjs, { loggedIn: false })
).toBeFalsy()
expect(isLogInSuccessHeaderPresent(ServerType.Sasjs, undefined)).toBeFalsy()
})
})

View File

@@ -1,7 +1,6 @@
import { SasAuthResponse } from '@sasjs/utils/types'
export const mockLoginAuthoriseRequiredResponse = `<form id="application_authorization" action="/SASLogon/oauth/authorize" method="POST"><input type="hidden" name="X-Uaa-Csrf" value="2nfuxIn6WaOURWL7tzTXCe"/>`
export const mockLoginSuccessResponse = `You have signed in`
export const mockAuthResponse: SasAuthResponse = {
access_token: 'acc355',

View File

@@ -3,6 +3,7 @@
*/
import { verifySas9Login } from '../verifySas9Login'
import * as delayModule from '../../utils/delay'
import { getExpectedLogInSuccessHeader } from '../'
describe('verifySas9Login', () => {
const serverUrl = 'http://test-server.com'
@@ -18,7 +19,9 @@ describe('verifySas9Login', () => {
const popup = {
window: {
location: { href: serverUrl + `/SASLogon` },
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
document: {
body: { innerText: `<h3>${getExpectedLogInSuccessHeader()}</h3>` }
}
}
} as unknown as Window

View File

@@ -3,6 +3,7 @@
*/
import { verifySasViyaLogin } from '../verifySasViyaLogin'
import * as delayModule from '../../utils/delay'
import { getExpectedLogInSuccessHeader } from '../'
describe('verifySasViyaLogin', () => {
const serverUrl = 'http://test-server.com'
@@ -19,7 +20,9 @@ describe('verifySasViyaLogin', () => {
const popup = {
window: {
location: { href: serverUrl + `/SASLogon` },
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
document: {
body: { innerText: `<h3>${getExpectedLogInSuccessHeader()}</h3>` }
}
}
} as unknown as Window

View File

@@ -1,4 +1,5 @@
import { delay } from '../utils'
import { getExpectedLogInSuccessHeader } from './'
export async function verifySas9Login(loginPopup: Window): Promise<{
isLoggedIn: boolean
@@ -6,13 +7,17 @@ export async function verifySas9Login(loginPopup: Window): Promise<{
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.')
loginPopup.window.document.body.innerText.includes(
getExpectedLogInSuccessHeader()
)
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
} while (!isLoggedIn && elapsedSeconds < 5 * 60)

View File

@@ -1,4 +1,5 @@
import { delay } from '../utils'
import { getExpectedLogInSuccessHeader } from './'
export async function verifySasViyaLogin(loginPopup: Window): Promise<{
isLoggedIn: boolean
@@ -6,23 +7,32 @@ export async function verifySasViyaLogin(loginPopup: Window): Promise<{
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.'
getExpectedLogInSuccessHeader()
)
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
} while (!isAuthorized && elapsedSeconds < 5 * 60)

View File

@@ -0,0 +1,10 @@
interface IEnavigator {
userLanguage?: string
}
/**
* Provides preferred language of the user.
* @returns A string representing the preferred language of the user, usually the language of the browser UI. Examples of valid language codes include "en", "en-US", "fr", "fr-FR", "es-ES". More info available https://datatracker.ietf.org/doc/html/rfc5646
*/
export const getUserLanguage = () =>
window.navigator.language || (window.navigator as IEnavigator).userLanguage

View File

@@ -20,3 +20,4 @@ export * from './serialize'
export * from './splitChunks'
export * from './validateInput'
export * from './getFormData'
export * from './getUserLanguage'

View File

@@ -27,6 +27,15 @@ const browserConfig = {
index: './src/index.ts',
minified_sas9: './src/minified/sas9/index.ts'
},
externals: {
'node:fs': 'node:fs',
'node:fs/promises': 'node:fs/promises',
'node:path': 'node:path',
'node:stream': 'node:stream',
'node:url': 'node:url',
'node:events': 'node:events',
'node:string_decoder': 'node:string_decoder'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build'),