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

chore(git): Merge branch 'master' into auto-tests

This commit is contained in:
2022-04-27 16:26:02 +02:00
306 changed files with 47729 additions and 92319 deletions

View File

@@ -1,11 +1,18 @@
import { ServerType } from '@sasjs/utils/types'
import { RequestClient } from '../request/RequestClient'
import { LoginOptions, LoginResult } from '../types/Login'
import { serialize } from '../utils'
import { getAccessTokenForSasjs } from './getAccessTokenForSasjs'
import { getAuthCodeForSasjs } from './getAuthCodeForSasjs'
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,
@@ -16,68 +23,175 @@ export class AuthManager {
this.logoutUrl =
this.serverType === ServerType.Sas9
? '/SASLogon/logout?'
: '/SASLogon/logout.do?'
: this.serverType === ServerType.SasViya
? '/SASLogon/logout.do?'
: '/SASjsApi/auth/logout'
}
/**
* 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.
* @param clientId - a string representing the client ID.
* @returns - a boolean `isLoggedin` and a string `username`
*/
public async logInSasjs(
username: string,
password: string,
clientId: string
): Promise<LoginResult> {
const isLoggedIn = await this.sendLoginRequestSasjs(
username,
password,
clientId
)
.then((res) => {
this.userName = username
this.requestClient.saveLocalStorageToken(
res.access_token,
res.refresh_token
)
return true
})
.catch(() => false)
return {
isLoggedIn,
userName: this.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 }
@@ -101,17 +215,70 @@ export class AuthManager {
return loginResponse
}
private async sendLoginRequestSasjs(
username: string,
password: string,
clientId: string
) {
const authCode = await getAuthCodeForSasjs(
this.requestClient,
username,
password,
clientId
)
return getAccessTokenForSasjs(this.requestClient, clientId, authCode)
}
/**
* 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() {
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
//For SAS9 we will send request on SASStoredProcess
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()
if (this.serverType !== ServerType.Sasjs)
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
}> {
const url =
this.serverType === 'SASVIYA'
? `${this.serverUrl}/identities`
: `${this.serverUrl}/SASStoredProcess`
this.serverType === ServerType.SasViya
? `${this.serverUrl}/identities/users/@currentUser`
: this.serverType === ServerType.Sas9
? `${this.serverUrl}/SASStoredProcess`
: `${this.serverUrl}/SASjsApi/session`
const { result: loginResponse } = await this.requestClient
.get<string>(url, undefined, 'text/plain')
@@ -120,27 +287,40 @@ 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()
const { result: formResponse } = await this.requestClient.get<string>(
this.loginUrl.replace('.do', ''),
undefined,
'text/plain'
)
loginForm = await this.getLoginForm(formResponse)
}
return { isLoggedIn, userName }
}
return Promise.resolve({
isLoggedIn,
userName: this.userName,
loginForm
})
private extractUserName = (response: any): string => {
switch (this.serverType) {
case ServerType.SasViya:
return response?.id
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('')
case ServerType.Sasjs:
return response?.username
default:
console.error('Server Type not found in extractUserName function')
return ''
}
}
private getLoginForm(response: any) {
@@ -186,9 +366,21 @@ export class AuthManager {
/**
* Logs out of the configured SAS server.
* @param accessToken - an optional access token is required for SASjs server type.
*/
public logOut() {
public async logOut() {
if (this.serverType === ServerType.Sasjs) {
return this.requestClient
.delete(this.logoutUrl)
.catch(() => true)
.finally(() => {
this.requestClient.clearLocalStorageTokens()
return true
})
}
this.requestClient.clearCsrfTokens()
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
}
}

View File

@@ -0,0 +1,36 @@
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
/**
* Exchanges the auth code for an access token for the given client.
* @param requestClient - the pre-configured HTTP request client
* @param clientId - the client ID to authenticate with.
* @param authCode - the auth code received from the server.
*/
export async function getAccessTokenForSasjs(
requestClient: RequestClient,
clientId: string,
authCode: string
) {
const url = '/SASjsApi/auth/token'
const data = {
clientId,
code: authCode
}
return await requestClient
.post(url, data, undefined)
.then((res) => {
const sasAuth = res.result as {
accessToken: string
refreshToken: string
}
return {
access_token: sasAuth.accessToken,
refresh_token: sasAuth.refreshToken
}
})
.catch((err) => {
throw prefixMessage(err, 'Error while getting access token. ')
})
}

View File

@@ -1,7 +1,7 @@
import { SasAuthResponse } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error'
import * as NodeFormData from 'form-data'
import { RequestClient } from '../request/RequestClient'
import { CertificateError } from '../types/errors'
/**
* Exchanges the auth code for an access token for the given client.
@@ -10,7 +10,7 @@ import { RequestClient } from '../request/RequestClient'
* @param clientSecret - the client secret to authenticate with.
* @param authCode - the auth code received from the server.
*/
export async function getAccessToken(
export async function getAccessTokenForViya(
requestClient: RequestClient,
clientId: string,
clientSecret: string,
@@ -24,29 +24,21 @@ export async function getAccessToken(
token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
}
const headers = {
Authorization: 'Basic ' + token
Authorization: 'Basic ' + token,
Accept: 'application/json'
}
let formData
if (typeof FormData === 'undefined') {
formData = new NodeFormData()
} else {
formData = new FormData()
}
formData.append('grant_type', 'authorization_code')
formData.append('code', authCode)
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: authCode
})
const authResponse = await requestClient
.post(
url,
formData,
undefined,
'multipart/form-data; boundary=' + (formData as any)._boundary,
headers
)
.post(url, data, undefined, 'application/x-www-form-urlencoded', headers)
.then((res) => res.result as SasAuthResponse)
.catch((err) => {
throw prefixMessage(err, 'Error while getting access token')
if (err instanceof CertificateError) throw err
throw prefixMessage(err, 'Error while getting access token. ')
})
return authResponse

View File

@@ -0,0 +1,31 @@
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
/**
* Performs a login authenticate and returns an auth code for the given client.
* @param requestClient - the pre-configured HTTP request client
* @param username - a string representing the username.
* @param password - a string representing the password.
* @param clientId - the client ID to authenticate with.
*/
export const getAuthCodeForSasjs = async (
requestClient: RequestClient,
username: string,
password: string,
clientId: string
) => {
const url = '/SASjsApi/auth/authorize'
const data = { username, password, clientId }
const { code: authCode } = await requestClient
.post<{ code: string }>(url, data, undefined)
.then((res) => res.result)
.catch((err) => {
throw prefixMessage(
err,
'Error while authenticating with provided username, password and clientId. '
)
})
return authCode
}

View File

@@ -3,18 +3,21 @@ import {
isRefreshTokenExpiring,
hasTokenExpired
} from '@sasjs/utils/auth'
import { AuthConfig } from '@sasjs/utils/types'
import { AuthConfig, ServerType } from '@sasjs/utils/types'
import { RequestClient } from '../request/RequestClient'
import { refreshTokens } from './refreshTokens'
import { refreshTokensForViya } from './refreshTokensForViya'
import { refreshTokensForSasjs } from './refreshTokensForSasjs'
/**
* Returns the auth configuration, refreshing the tokens if necessary.
* @param requestClient - the pre-configured HTTP request client
* @param authConfig - an object containing a client ID, secret, access token and refresh token
* @param serverType - server type for which refreshing the tokens, defaults to SASVIYA
*/
export async function getTokens(
requestClient: RequestClient,
authConfig: AuthConfig
authConfig: AuthConfig,
serverType: ServerType = ServerType.SasViya
): Promise<AuthConfig> {
const logger = process.logger || console
let { access_token, refresh_token, client, secret } = authConfig
@@ -29,12 +32,16 @@ export async function getTokens(
throw new Error(error)
}
logger.info('Refreshing access and refresh tokens.')
;({ access_token, refresh_token } = await refreshTokens(
requestClient,
client,
secret,
refresh_token
))
const tokens =
serverType === ServerType.SasViya
? await refreshTokensForViya(
requestClient,
client,
secret,
refresh_token
)
: await refreshTokensForSasjs(requestClient, refresh_token)
;({ access_token, refresh_token } = tokens)
}
return { access_token, refresh_token, client, secret }
}

40
src/auth/openWebPage.ts Normal file
View 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
}

View File

@@ -0,0 +1,35 @@
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
/**
* Exchanges the refresh token for an access token for the given client.
* @param requestClient - the pre-configured HTTP request client
* @param refreshToken - the refresh token received from the server.
*/
export async function refreshTokensForSasjs(
requestClient: RequestClient,
refreshToken: string
) {
const url = '/SASjsApi/auth/refresh'
const headers = {
Authorization: 'Bearer ' + refreshToken
}
const authResponse = await requestClient
.post(url, undefined, undefined, undefined, headers)
.then((res) => {
const sasAuth = res.result as {
accessToken: string
refreshToken: string
}
return {
access_token: sasAuth.accessToken,
refresh_token: sasAuth.refreshToken
}
})
.catch((err) => {
throw prefixMessage(err, 'Error while refreshing tokens')
})
return authResponse
}

View File

@@ -8,9 +8,9 @@ import { RequestClient } from '../request/RequestClient'
* @param requestClient - the pre-configured HTTP request client
* @param clientId - the client ID to authenticate with.
* @param clientSecret - the client secret to authenticate with.
* @param authCode - the refresh token received from the server.
* @param refreshToken - the refresh token received from the server.
*/
export async function refreshTokens(
export async function refreshTokensForViya(
requestClient: RequestClient,
clientId: string,
clientSecret: string,

View File

@@ -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'
}

View File

@@ -0,0 +1,65 @@
import { AuthConfig } from '@sasjs/utils'
import { generateToken, mockSasjsAuthResponse } from './mockResponses'
import { RequestClient } from '../../request/RequestClient'
import { getAccessTokenForSasjs } from '../getAccessTokenForSasjs'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
describe('getAccessTokenForSasjs', () => {
it('should attempt to refresh tokens', async () => {
setupMocks()
const access_token = generateToken(30)
const refresh_token = generateToken(30)
const authConfig: AuthConfig = {
access_token,
refresh_token,
client: 'cl13nt',
secret: 's3cr3t'
}
jest
.spyOn(requestClient, 'post')
.mockImplementation(() =>
Promise.resolve({ result: mockSasjsAuthResponse, etag: '' })
)
await getAccessTokenForSasjs(
requestClient,
authConfig.client,
authConfig.refresh_token
)
expect(requestClient.post).toHaveBeenCalledWith(
'/SASjsApi/auth/token',
{ clientId: authConfig.client, code: authConfig.refresh_token },
undefined
)
})
it('should handle errors while refreshing tokens', async () => {
setupMocks()
const access_token = generateToken(30)
const refresh_token = generateToken(30)
const authConfig: AuthConfig = {
access_token,
refresh_token,
client: 'cl13nt',
secret: 's3cr3t'
}
jest
.spyOn(requestClient, 'post')
.mockImplementation(() => Promise.reject('Token Error'))
const error = await getAccessTokenForSasjs(
requestClient,
authConfig.client,
authConfig.refresh_token
).catch((e) => e)
expect(error).toContain('Error while getting access token')
})
})
const setupMocks = () => {
jest.restoreAllMocks()
jest.mock('../../request/RequestClient')
}

View File

@@ -2,11 +2,11 @@ import { AuthConfig } from '@sasjs/utils'
import * as NodeFormData from 'form-data'
import { generateToken, mockAuthResponse } from './mockResponses'
import { RequestClient } from '../../request/RequestClient'
import { getAccessToken } from '../getAccessToken'
import { getAccessTokenForViya } from '../getAccessTokenForViya'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
describe('getAccessToken', () => {
describe('getAccessTokenForViya', () => {
it('should attempt to refresh tokens', async () => {
setupMocks()
const access_token = generateToken(30)
@@ -26,7 +26,7 @@ describe('getAccessToken', () => {
authConfig.client + ':' + authConfig.secret
).toString('base64')
await getAccessToken(
await getAccessTokenForViya(
requestClient,
authConfig.client,
authConfig.secret,
@@ -35,11 +35,12 @@ describe('getAccessToken', () => {
expect(requestClient.post).toHaveBeenCalledWith(
'/SASLogon/oauth/token',
expect.any(NodeFormData),
expect.any(URLSearchParams),
undefined,
expect.stringContaining('multipart/form-data; boundary='),
'application/x-www-form-urlencoded',
{
Authorization: 'Basic ' + token
Authorization: 'Basic ' + token,
Accept: 'application/json'
}
)
})
@@ -58,7 +59,7 @@ describe('getAccessToken', () => {
.spyOn(requestClient, 'post')
.mockImplementation(() => Promise.reject('Token Error'))
const error = await getAccessToken(
const error = await getAccessTokenForViya(
requestClient,
authConfig.client,
authConfig.secret,

View File

@@ -1,5 +1,5 @@
import { AuthConfig } from '@sasjs/utils'
import * as refreshTokensModule from '../refreshTokens'
import * as refreshTokensModule from '../refreshTokensForViya'
import { generateToken, mockAuthResponse } from './mockResponses'
import { getTokens } from '../getTokens'
import { RequestClient } from '../../request/RequestClient'
@@ -20,7 +20,7 @@ describe('getTokens', () => {
await getTokens(requestClient, authConfig)
expect(refreshTokensModule.refreshTokens).toHaveBeenCalledWith(
expect(refreshTokensModule.refreshTokensForViya).toHaveBeenCalledWith(
requestClient,
authConfig.client,
authConfig.secret,
@@ -41,7 +41,7 @@ describe('getTokens', () => {
await getTokens(requestClient, authConfig)
expect(refreshTokensModule.refreshTokens).toHaveBeenCalledWith(
expect(refreshTokensModule.refreshTokensForViya).toHaveBeenCalledWith(
requestClient,
authConfig.client,
authConfig.secret,
@@ -71,9 +71,9 @@ describe('getTokens', () => {
const setupMocks = () => {
jest.restoreAllMocks()
jest.mock('../../request/RequestClient')
jest.mock('../refreshTokens')
jest.mock('../refreshTokensForViya')
jest
.spyOn(refreshTokensModule, 'refreshTokens')
.spyOn(refreshTokensModule, 'refreshTokensForViya')
.mockImplementation(() => Promise.resolve(mockAuthResponse))
}

View File

@@ -13,6 +13,11 @@ export const mockAuthResponse: SasAuthResponse = {
jti: 'test'
}
export const mockSasjsAuthResponse = {
access_token: 'acc355',
refresh_token: 'r3fr35h'
}
export const generateToken = (timeToLiveSeconds: number): string => {
const exp =
new Date(new Date().getTime() + timeToLiveSeconds * 1000).getTime() / 1000
@@ -22,3 +27,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
})

View 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()
})
})
})

View File

@@ -0,0 +1,47 @@
import { generateToken, mockAuthResponse } from './mockResponses'
import { RequestClient } from '../../request/RequestClient'
import { refreshTokensForSasjs } from '../refreshTokensForSasjs'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
describe('refreshTokensForSasjs', () => {
it('should attempt to refresh tokens', async () => {
setupMocks()
const refresh_token = generateToken(30)
jest
.spyOn(requestClient, 'post')
.mockImplementation(() =>
Promise.resolve({ result: mockAuthResponse, etag: '' })
)
await refreshTokensForSasjs(requestClient, refresh_token)
expect(requestClient.post).toHaveBeenCalledWith(
'/SASjsApi/auth/refresh',
undefined,
undefined,
undefined,
{ Authorization: `Bearer ${refresh_token}` }
)
})
it('should handle errors while refreshing tokens', async () => {
setupMocks()
const refresh_token = generateToken(30)
jest
.spyOn(requestClient, 'post')
.mockImplementation(() => Promise.reject('Token Error'))
const error = await refreshTokensForSasjs(
requestClient,
refresh_token
).catch((e) => e)
expect(error).toContain('Error while refreshing tokens')
})
})
const setupMocks = () => {
jest.restoreAllMocks()
jest.mock('../../request/RequestClient')
}

View File

@@ -2,11 +2,11 @@ import { AuthConfig } from '@sasjs/utils'
import * as NodeFormData from 'form-data'
import { generateToken, mockAuthResponse } from './mockResponses'
import { RequestClient } from '../../request/RequestClient'
import { refreshTokens } from '../refreshTokens'
import { refreshTokensForViya } from '../refreshTokensForViya'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
describe('refreshTokens', () => {
describe('refreshTokensForViya', () => {
it('should attempt to refresh tokens', async () => {
setupMocks()
const access_token = generateToken(30)
@@ -26,7 +26,7 @@ describe('refreshTokens', () => {
authConfig.client + ':' + authConfig.secret
).toString('base64')
await refreshTokens(
await refreshTokensForViya(
requestClient,
authConfig.client,
authConfig.secret,
@@ -58,7 +58,7 @@ describe('refreshTokens', () => {
.spyOn(requestClient, 'post')
.mockImplementation(() => Promise.reject('Token Error'))
const error = await refreshTokens(
const error = await refreshTokensForViya(
requestClient,
authConfig.client,
authConfig.secret,

View 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
})
})
})

View 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
})
})
})

View 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 }
}

View 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')