mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-02 18:20:06 +00:00
feat: login for web with server type SASJS
This commit is contained in:
30
src/SASjs.ts
30
src/SASjs.ts
@@ -21,6 +21,7 @@ import {
|
|||||||
SasAuthResponse
|
SasAuthResponse
|
||||||
} from '@sasjs/utils/types'
|
} from '@sasjs/utils/types'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
import { SasjsRequestClient } from './request/SasjsRequestClient'
|
||||||
import {
|
import {
|
||||||
JobExecutor,
|
JobExecutor,
|
||||||
WebJobExecutor,
|
WebJobExecutor,
|
||||||
@@ -547,29 +548,39 @@ export default class SASjs {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a session is active, or login is required.
|
* Checks whether a session is active, or login is required.
|
||||||
* @param accessToken - an optional access token is required for SASjs server type.
|
|
||||||
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||||
*/
|
*/
|
||||||
public async checkSession(accessToken?: string) {
|
public async checkSession() {
|
||||||
return this.authManager!.checkSession(accessToken)
|
return this.authManager!.checkSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs into the SAS server with the supplied credentials.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
|
* @param clientId - a string representing the client ID.
|
||||||
*/
|
*/
|
||||||
public async logIn(
|
public async logIn(
|
||||||
username?: string,
|
username?: string,
|
||||||
password?: string,
|
password?: string,
|
||||||
|
clientId?: string,
|
||||||
options: LoginOptions = {}
|
options: LoginOptions = {}
|
||||||
): Promise<LoginResult> {
|
): Promise<LoginResult> {
|
||||||
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
||||||
if (!username || !password) {
|
if (!username || !password)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'A username and password are required when using the default login mechanism.'
|
'A username and password are required when using the default login mechanism.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
|
||||||
|
if (!clientId)
|
||||||
|
throw new Error(
|
||||||
|
'A username, password and clientId are required when using the default login mechanism with server type SASJS.'
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.authManager!.logInSasjs(username, password, clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.authManager!.logIn(username, password)
|
return this.authManager!.logIn(username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,10 +595,9 @@ export default class SASjs {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs out of the configured SAS server.
|
* Logs out of the configured SAS server.
|
||||||
* @param accessToken - an optional access token is required for SASjs server type.
|
|
||||||
*/
|
*/
|
||||||
public logOut(accessToken?: string) {
|
public logOut() {
|
||||||
return this.authManager!.logOut(accessToken)
|
return this.authManager!.logOut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -983,7 +993,11 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.requestClient) {
|
if (!this.requestClient) {
|
||||||
this.requestClient = new RequestClient(
|
const RequestClientClass =
|
||||||
|
this.sasjsConfig.serverType === ServerType.Sasjs
|
||||||
|
? SasjsRequestClient
|
||||||
|
: RequestClient
|
||||||
|
this.requestClient = new RequestClientClass(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.httpsAgentOptions
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { FolderMember, ServiceMember, ExecutionQuery } from './types'
|
|||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
||||||
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
||||||
|
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
|
||||||
|
|
||||||
export class SASjsApiClient {
|
export class SASjsApiClient {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -58,6 +59,20 @@ export class SASjsApiClient {
|
|||||||
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
|
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
|
||||||
return refreshTokensForSasjs(this.requestClient, refreshToken)
|
return refreshTokensForSasjs(this.requestClient, refreshToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a login authenticate and returns an auth code for the given client.
|
||||||
|
* @param username - a string representing the username.
|
||||||
|
* @param password - a string representing the password.
|
||||||
|
* @param clientId - the client ID to authenticate with.
|
||||||
|
*/
|
||||||
|
public async getAuthCode(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
clientId: string
|
||||||
|
) {
|
||||||
|
return getAuthCodeForSasjs(this.requestClient, username, password, clientId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo move to sasjs/utils
|
// todo move to sasjs/utils
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { ServerType } from '@sasjs/utils/types'
|
|||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { LoginOptions, LoginResult } from '../types/Login'
|
import { LoginOptions, LoginResult } from '../types/Login'
|
||||||
import { serialize } from '../utils'
|
import { serialize } from '../utils'
|
||||||
|
import { getAccessTokenForSasjs } from './getAccessTokenForSasjs'
|
||||||
|
import { getAuthCodeForSasjs } from './getAuthCodeForSasjs'
|
||||||
import { openWebPage } from './openWebPage'
|
import { openWebPage } from './openWebPage'
|
||||||
import { verifySas9Login } from './verifySas9Login'
|
import { verifySas9Login } from './verifySas9Login'
|
||||||
import { verifySasViyaLogin } from './verifySasViyaLogin'
|
import { verifySasViyaLogin } from './verifySasViyaLogin'
|
||||||
@@ -81,6 +83,39 @@ export class AuthManager {
|
|||||||
return { isLoggedIn: false, 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.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
@@ -180,28 +215,41 @@ export class AuthManager {
|
|||||||
return loginResponse
|
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.
|
* Checks whether a session is active, or login is required.
|
||||||
* @param accessToken - an optional access token is required for SASjs server type.
|
|
||||||
* @returns - a promise which resolves with an object containing three values
|
* @returns - a promise which resolves with an object containing three values
|
||||||
* - a boolean `isLoggedIn`
|
* - a boolean `isLoggedIn`
|
||||||
* - a string `userName` and
|
* - a string `userName` and
|
||||||
* - a form `loginForm` if not loggedin.
|
* - a form `loginForm` if not loggedin.
|
||||||
*/
|
*/
|
||||||
public async checkSession(accessToken?: string): Promise<{
|
public async checkSession(): Promise<{
|
||||||
isLoggedIn: boolean
|
isLoggedIn: boolean
|
||||||
userName: string
|
userName: string
|
||||||
loginForm?: any
|
loginForm?: any
|
||||||
}> {
|
}> {
|
||||||
const { isLoggedIn, userName } = await this.fetchUserName(accessToken)
|
const { isLoggedIn, userName } = await this.fetchUserName()
|
||||||
let loginForm = null
|
let loginForm = null
|
||||||
|
|
||||||
if (!isLoggedIn && this.serverType !== ServerType.Sasjs) {
|
if (!isLoggedIn) {
|
||||||
//We will logout to make sure cookies are removed and login form is presented
|
//We will logout to make sure cookies are removed and login form is presented
|
||||||
//Residue can happen in case of session expiration
|
//Residue can happen in case of session expiration
|
||||||
await this.logOut()
|
await this.logOut()
|
||||||
|
|
||||||
loginForm = await this.getNewLoginForm()
|
if (this.serverType !== ServerType.Sasjs)
|
||||||
|
loginForm = await this.getNewLoginForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
@@ -221,7 +269,7 @@ export class AuthManager {
|
|||||||
return await this.getLoginForm(formResponse)
|
return await this.getLoginForm(formResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchUserName(accessToken?: string): Promise<{
|
private async fetchUserName(): Promise<{
|
||||||
isLoggedIn: boolean
|
isLoggedIn: boolean
|
||||||
userName: string
|
userName: string
|
||||||
}> {
|
}> {
|
||||||
@@ -232,9 +280,8 @@ export class AuthManager {
|
|||||||
? `${this.serverUrl}/SASStoredProcess`
|
? `${this.serverUrl}/SASStoredProcess`
|
||||||
: `${this.serverUrl}/SASjsApi/session`
|
: `${this.serverUrl}/SASjsApi/session`
|
||||||
|
|
||||||
// Access token is required for server type `SASjs`
|
|
||||||
const { result: loginResponse } = await this.requestClient
|
const { result: loginResponse } = await this.requestClient
|
||||||
.get<string>(url, accessToken, 'text/plain')
|
.get<string>(url, undefined, 'text/plain')
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
return { result: 'authErr' }
|
return { result: 'authErr' }
|
||||||
})
|
})
|
||||||
@@ -315,11 +362,19 @@ export class AuthManager {
|
|||||||
* Logs out of the configured SAS server.
|
* Logs out of the configured SAS server.
|
||||||
* @param accessToken - an optional access token is required for SASjs server type.
|
* @param accessToken - an optional access token is required for SASjs server type.
|
||||||
*/
|
*/
|
||||||
public logOut(accessToken?: string) {
|
public async logOut() {
|
||||||
if (this.serverType === ServerType.Sasjs) {
|
if (this.serverType === ServerType.Sasjs) {
|
||||||
return this.requestClient.post(this.logoutUrl, undefined, accessToken)
|
return this.requestClient
|
||||||
|
.delete(this.logoutUrl)
|
||||||
|
.catch(() => true)
|
||||||
|
.finally(() => {
|
||||||
|
this.requestClient.clearLocalStorageTokens()
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestClient.clearCsrfTokens()
|
this.requestClient.clearCsrfTokens()
|
||||||
|
|
||||||
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
|
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/auth/getAuthCodeForSasjs.ts
Normal file
31
src/auth/getAuthCodeForSasjs.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -144,7 +144,6 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
// Access token is required for server type `SASjs`
|
|
||||||
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
||||||
.then(async (res: any) => {
|
.then(async (res: any) => {
|
||||||
const resObj =
|
const resObj =
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ export interface HttpClient {
|
|||||||
): Promise<{ result: T; etag: string }>
|
): Promise<{ result: T; etag: string }>
|
||||||
|
|
||||||
getCsrfToken(type: 'general' | 'file'): CsrfToken | undefined
|
getCsrfToken(type: 'general' | 'file'): CsrfToken | undefined
|
||||||
|
saveLocalStorageToken(accessToken: string, refreshToken: string): void
|
||||||
clearCsrfTokens(): void
|
clearCsrfTokens(): void
|
||||||
|
clearLocalStorageTokens(): void
|
||||||
getBaseUrl(): string
|
getBaseUrl(): string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +72,11 @@ export class RequestClient implements HttpClient {
|
|||||||
this.createHttpClient(baseUrl, httpsAgentOptions)
|
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public saveLocalStorageToken(accessToken: string, refreshToken: string) {
|
||||||
|
localStorage.setItem('accessToken', accessToken)
|
||||||
|
localStorage.setItem('refreshToken', refreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||||
return type === 'file' ? this.fileUploadCsrfToken : this.csrfToken
|
return type === 'file' ? this.fileUploadCsrfToken : this.csrfToken
|
||||||
}
|
}
|
||||||
@@ -78,6 +85,10 @@ export class RequestClient implements HttpClient {
|
|||||||
this.csrfToken = { headerName: '', value: '' }
|
this.csrfToken = { headerName: '', value: '' }
|
||||||
this.fileUploadCsrfToken = { headerName: '', value: '' }
|
this.fileUploadCsrfToken = { headerName: '', value: '' }
|
||||||
}
|
}
|
||||||
|
public clearLocalStorageTokens() {
|
||||||
|
localStorage.setItem('accessToken', '')
|
||||||
|
localStorage.setItem('refreshToken', '')
|
||||||
|
}
|
||||||
|
|
||||||
public getBaseUrl() {
|
public getBaseUrl() {
|
||||||
return this.httpClient.defaults.baseURL || ''
|
return this.httpClient.defaults.baseURL || ''
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export class Sas9RequestClient extends RequestClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public post<T>(
|
public async post<T>(
|
||||||
url: string,
|
url: string,
|
||||||
data: any,
|
data: any,
|
||||||
accessToken: string | undefined,
|
accessToken: string | undefined,
|
||||||
|
|||||||
23
src/request/SasjsRequestClient.ts
Normal file
23
src/request/SasjsRequestClient.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { RequestClient } from './RequestClient'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific request client for SASJS.
|
||||||
|
* Append tokens in headers.
|
||||||
|
*/
|
||||||
|
export class SasjsRequestClient extends RequestClient {
|
||||||
|
getHeaders = (accessToken: string | undefined, contentType: string) => {
|
||||||
|
const headers: any = {}
|
||||||
|
|
||||||
|
if (contentType !== 'application/x-www-form-urlencoded')
|
||||||
|
headers['Content-Type'] = contentType
|
||||||
|
|
||||||
|
headers.Accept = contentType === 'application/json' ? contentType : '*/*'
|
||||||
|
|
||||||
|
if (!accessToken)
|
||||||
|
accessToken = localStorage.getItem('accessToken') ?? undefined
|
||||||
|
|
||||||
|
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user