1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 09:24:35 +00:00

feat: login for web with server type SASJS

This commit is contained in:
Saad Jutt
2021-12-09 17:13:47 +05:00
parent ebe9c2ffeb
commit c56874fe00
8 changed files with 168 additions and 20 deletions

View File

@@ -21,6 +21,7 @@ import {
SasAuthResponse
} from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient'
import { SasjsRequestClient } from './request/SasjsRequestClient'
import {
JobExecutor,
WebJobExecutor,
@@ -547,29 +548,39 @@ export default class SASjs {
/**
* 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`.
*/
public async checkSession(accessToken?: string) {
return this.authManager!.checkSession(accessToken)
public async checkSession() {
return this.authManager!.checkSession()
}
/**
* 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.
*/
public async logIn(
username?: string,
password?: string,
clientId?: string,
options: LoginOptions = {}
): Promise<LoginResult> {
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
if (!username || !password) {
if (!username || !password)
throw new Error(
'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)
}
@@ -584,10 +595,9 @@ export default class SASjs {
/**
* Logs out of the configured SAS server.
* @param accessToken - an optional access token is required for SASjs server type.
*/
public logOut(accessToken?: string) {
return this.authManager!.logOut(accessToken)
public logOut() {
return this.authManager!.logOut()
}
/**
@@ -983,7 +993,11 @@ export default class SASjs {
}
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.httpsAgentOptions
)

View File

@@ -2,6 +2,7 @@ import { FolderMember, ServiceMember, ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
export class SASjsApiClient {
constructor(
@@ -58,6 +59,20 @@ export class SASjsApiClient {
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
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

View File

@@ -2,6 +2,8 @@ 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'
@@ -81,6 +83,39 @@ export class AuthManager {
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.
@@ -180,27 +215,40 @@ 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.
* @param accessToken - an optional access token is required for SASjs server type.
* @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(accessToken?: string): Promise<{
public async checkSession(): Promise<{
isLoggedIn: boolean
userName: string
loginForm?: any
}> {
const { isLoggedIn, userName } = await this.fetchUserName(accessToken)
const { isLoggedIn, userName } = await this.fetchUserName()
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
//Residue can happen in case of session expiration
await this.logOut()
if (this.serverType !== ServerType.Sasjs)
loginForm = await this.getNewLoginForm()
}
@@ -221,7 +269,7 @@ export class AuthManager {
return await this.getLoginForm(formResponse)
}
private async fetchUserName(accessToken?: string): Promise<{
private async fetchUserName(): Promise<{
isLoggedIn: boolean
userName: string
}> {
@@ -232,9 +280,8 @@ export class AuthManager {
? `${this.serverUrl}/SASStoredProcess`
: `${this.serverUrl}/SASjsApi/session`
// Access token is required for server type `SASjs`
const { result: loginResponse } = await this.requestClient
.get<string>(url, accessToken, 'text/plain')
.get<string>(url, undefined, 'text/plain')
.catch((err: any) => {
return { result: 'authErr' }
})
@@ -315,11 +362,19 @@ export class AuthManager {
* Logs out of the configured SAS server.
* @param accessToken - an optional access token is required for SASjs server type.
*/
public logOut(accessToken?: string) {
public async logOut() {
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()
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
}
}

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

@@ -144,7 +144,6 @@ export class WebJobExecutor extends BaseJobExecutor {
}
const requestPromise = new Promise((resolve, reject) => {
// Access token is required for server type `SASjs`
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
.then(async (res: any) => {
const resObj =

View File

@@ -48,7 +48,9 @@ export interface HttpClient {
): Promise<{ result: T; etag: string }>
getCsrfToken(type: 'general' | 'file'): CsrfToken | undefined
saveLocalStorageToken(accessToken: string, refreshToken: string): void
clearCsrfTokens(): void
clearLocalStorageTokens(): void
getBaseUrl(): string
}
@@ -70,6 +72,11 @@ export class RequestClient implements HttpClient {
this.createHttpClient(baseUrl, httpsAgentOptions)
}
public saveLocalStorageToken(accessToken: string, refreshToken: string) {
localStorage.setItem('accessToken', accessToken)
localStorage.setItem('refreshToken', refreshToken)
}
public getCsrfToken(type: 'general' | 'file' = 'general') {
return type === 'file' ? this.fileUploadCsrfToken : this.csrfToken
}
@@ -78,6 +85,10 @@ export class RequestClient implements HttpClient {
this.csrfToken = { headerName: '', value: '' }
this.fileUploadCsrfToken = { headerName: '', value: '' }
}
public clearLocalStorageTokens() {
localStorage.setItem('accessToken', '')
localStorage.setItem('refreshToken', '')
}
public getBaseUrl() {
return this.httpClient.defaults.baseURL || ''

View File

@@ -87,7 +87,7 @@ export class Sas9RequestClient extends RequestClient {
})
}
public post<T>(
public async post<T>(
url: string,
data: any,
accessToken: string | undefined,

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