Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 11 additions & 218 deletions packages/las-sdk-browser/src/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,229 +1,22 @@
import axios from 'axios';
import { SHA256 } from 'crypto-js';
import * as base64 from 'crypto-js/enc-base64';
import { Credentials, Token, TokenStorage } from '@lucidtech/las-sdk-core';
import { Credentials, Token } from '@lucidtech/las-sdk-core';

const utils = {
randomString(alphabet: string, length: number) {
const buffer = new Uint8Array(length);
window.crypto.getRandomValues(buffer);
const chars: string[] = [];
buffer.forEach((v) => chars.push(alphabet[v % alphabet.length]));
return chars.join('');
},
};
export class TokenCredentials extends Credentials {
readonly organizationId: string;

export default utils;

export class PKCE {
static readonly CODE_CHALLENGE_ALPHABET: string =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._~-';
static readonly CODE_CHALLENGE_LENGTH: number = 64;
static readonly VERIFIER_KEY: string = 'pkceVerifier';
static readonly CHALLENGE_KEY: string = 'pkceChallenge';

readonly verifier: string;
readonly challenge: string;

constructor(verifier?: string, challenge?: string) {
this.verifier = verifier || PKCE.generateVerifier();
this.challenge = challenge || PKCE.generateChallenge(this.verifier);

window.sessionStorage.setItem(PKCE.VERIFIER_KEY, this.verifier);
window.sessionStorage.setItem(PKCE.CHALLENGE_KEY, this.challenge);
constructor(apiEndpoint: string, accessToken: string, organizationId: string) {
super(apiEndpoint, undefined, new Token(accessToken));
this.organizationId = organizationId;
}

private static generateVerifier(): string {
return utils.randomString(PKCE.CODE_CHALLENGE_ALPHABET, PKCE.CODE_CHALLENGE_LENGTH);
}

private static generateChallenge(verifier: string): string {
return SHA256(verifier).toString(base64).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
}

export class PKCEDerived extends PKCE {
readonly code: string;

constructor(verifier: string, challenge: string, code: string) {
super(verifier, challenge);
this.code = code;
}

static createFromCode(code?: string): PKCEDerived | null {
const verifier = window.sessionStorage.getItem(PKCE.VERIFIER_KEY);
const challenge = window.sessionStorage.getItem(PKCE.CHALLENGE_KEY);

if (!!code && !!verifier && !!challenge) {
return new PKCEDerived(verifier, challenge, code);
}

return null;
}
}

export class AuthorizationCodeCredentials extends Credentials {
readonly clientId: string;
private readonly authEndpoint: string;
private readonly redirectUri: string;
private readonly logoutRedirectUri?: string;
private readonly launchUriFn: (uri: string) => void;
pkce?: PKCEDerived | null;

constructor(
apiEndpoint: string,
clientId: string,
authEndpoint: string,
redirectUri: string,
launchUriFn: (uri: string) => void,
pkce?: PKCEDerived,
storage?: TokenStorage<Token>,
logoutRedirectUri?: string,
) {
super(apiEndpoint, storage);

this.clientId = clientId;
this.authEndpoint = authEndpoint;
this.redirectUri = redirectUri;
this.logoutRedirectUri = logoutRedirectUri;
this.launchUriFn = launchUriFn;
this.pkce = pkce;
}

protected getToken(): Promise<Token> {
return new Promise<Token>((resolve, reject) => {
this.refreshToken()
.then((token) => {
resolve(token);
})
.catch((_error) => {
this.getTokenFromCode()
.then((token) => {
resolve(token);
})
.catch((error) => {
this.initiateOAuthFlow();
reject(error);
});
});
getAccessToken(): Promise<string> {
return new Promise<string>((resolve, reject) => {
resolve(this.token.accessToken);
});
}

protected refreshToken(): Promise<Token> {
return new Promise<Token>((resolve, reject) => {
let { token } = this;

if (!token && !!this.storage) {
token = this.storage.getPersistentToken() || undefined;
}

if (!(token && token.refreshToken)) {
return reject({ message: 'No refresh token available' });
}

console.debug('Getting accessToken using refreshToken');
const params = {
grant_type: 'refresh_token',
client_id: this.clientId,
refresh_token: token.refreshToken,
};

this.postToTokenEndpoint(params)
.then((newToken) => {
resolve(new Token(newToken.accessToken, newToken.expiration, newToken.refreshToken || token.refreshToken));
})
.catch((error) => {
console.debug(`Error getting accessToken using refreshToken: ${error.message}`);
reject(error);
});
});
}

private getTokenFromCode(): Promise<Token> {
return new Promise<Token>((resolve, reject) => {
console.debug('Getting accessToken using PKCE code');

if (this.pkce) {
const params = {
grant_type: 'authorization_code',
code: this.pkce.code,
client_id: this.clientId,
redirect_uri: this.redirectUri,
code_verifier: this.pkce.verifier,
};

this.postToTokenEndpoint(params)
.then((token) => {
resolve(token);
})
.catch((error) => {
console.debug(`Error getting accessToken using PKCE code ${error.message}`);
reject(error);
});
} else {
reject({ message: 'No PKCE code available' });
}
});
}

private postToTokenEndpoint(params: any): Promise<Token> {
protected getToken(): Promise<Token> {
return new Promise<Token>((resolve, reject) => {
const endpoint = `https://${this.authEndpoint}/oauth2/token`;
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
const config = { headers, params };

axios
.post(endpoint, null, config)
.then((response) => {
const token = new Token(
response.data.access_token,
Date.now() + 1000 * response.data.expires_in,
response.data.refresh_token,
);
resolve(token);
})
.catch((error) => {
reject(error);
});
resolve(this.token);
});
}

initiateOAuthFlow(csrfToken?: string): void {
const pkce = new PKCE();

const params: any = {
response_type: 'code',
client_id: this.clientId,
redirect_uri: this.redirectUri,
code_challenge_method: 'S256',
code_challenge: pkce.challenge,
};

if (csrfToken) {
params.state = csrfToken;
}

const endpoint = `https://${this.authEndpoint}/oauth2/authorize`;
const config = { url: endpoint, params };
const uri = axios.getUri(config);
this.launchUriFn(uri);
}

initiateLogoutFlow(): void {
const params = {
client_id: this.clientId,
logout_uri: this.logoutRedirectUri || this.redirectUri,
};

const endpoint = `https://${this.authEndpoint}/logout`;
const config = { url: endpoint, params };
const uri = axios.getUri(config);
this.token = undefined;
this.pkce = undefined;
this.launchUriFn(uri);
}

isLoggedIn(): boolean {
return !!this.token || !!this.pkce;
}
}
2 changes: 1 addition & 1 deletion packages/las-sdk-browser/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { AuthorizationCodeCredentials, PKCEDerived } from './credentials';
export { TokenCredentials } from './credentials';
export { SessionStorage } from './storage';
export { Client, Credentials, Token, TokenStorage } from '@lucidtech/las-sdk-core';
export * from '@lucidtech/las-sdk-core';
12 changes: 6 additions & 6 deletions packages/las-sdk-core/src/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import { TokenStorage } from './storage';
*/
export class Token {
readonly accessToken: string;
readonly expiration: number;
readonly expiration?: number;
readonly refreshToken?: string;

/**
* Checks if current timestamp is larger than token expiration time
*/
isValid(): boolean {
return Date.now() < this.expiration;
return Date.now() < (this.expiration ?? 0);
}

/**
* @param {string} accessToken
* @param {number} expiration
* @param {string} [refreshToken]
* @param {string} refreshToken
*/
constructor(accessToken: string, expiration: number, refreshToken?: string) {
constructor(accessToken: string, expiration?: number, refreshToken?: string) {
this.accessToken = accessToken;
this.expiration = expiration;
this.refreshToken = refreshToken;
Expand All @@ -36,10 +36,10 @@ export abstract class Credentials {
protected token?: Token | null;
protected storage?: TokenStorage<Token>;

protected constructor(apiEndpoint: string, storage?: TokenStorage<Token>) {
protected constructor(apiEndpoint: string, storage?: TokenStorage<Token>, token?: Token | null) {
this.apiEndpoint = apiEndpoint;
this.storage = storage;
this.token = storage.getPersistentToken();
this.token = token ?? storage?.getPersistentToken();
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/las-sdk-core/src/types/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { JSONObject, OptionsOmit, PaginationOptions, RequestConfig } from './com

export type Action = {
/* Id */
id: string;
actionId: string;
/* Attributes */
config: JSONObject;
Expand All @@ -13,7 +14,6 @@ export type Action = {
functionId: string;
metadata: JSONObject;
name?: string | null;
agentId: string;
secretId?: string | null;
updatedBy?: string | null;
updatedTime?: Date | null;
Expand All @@ -28,6 +28,6 @@ export type ListActionsOptions = RequestConfig & PaginationOptions;
export type GetActionOptions = RequestConfig;
export type CreateActionOptions = RequestConfig &
OptionsOmit<Action, 'actionId'> &
Pick<Action, 'config' | 'functionId' | 'agentId'>;
Pick<Action, 'config' | 'functionId'>;
export type UpdateActionOptions = RequestConfig & OptionsOmit<Action, 'actionId'>;
export type DeleteActionOptions = RequestConfig;
6 changes: 3 additions & 3 deletions packages/las-sdk-core/src/types/actionRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ActionRunStatus = (typeof ActionRunStatusValues)[number];

export type ActionRun = {
/* Id */
id: string;
actionId: string;
runId: string;
/* Attributes */
Expand All @@ -15,8 +16,7 @@ export type ActionRun = {
logId: string;
metadata: JSONObject;
output?: JSONObject | null;
agentId: string;
agentRunId: string;
agentRunId?: string | null;
status: ActionRunStatus;
updatedBy?: string | null;
updatedTime?: Date | null;
Expand All @@ -31,6 +31,6 @@ export type ListActionRunsOptions = RequestConfig & PaginationOptions;
export type GetActionRunOptions = RequestConfig;
export type CreateActionRunOptions = RequestConfig &
OptionsOmit<ActionRun, 'actionId' | 'runId' | 'logId'> &
Pick<ActionRun, 'input' | 'agentId' | 'agentRunId'>;
Pick<ActionRun, 'input'>;
export type UpdateActionRunOptions = RequestConfig & Pick<Partial<ActionRun>, 'output' | 'metadata'>;
export type DeleteActionRunOptions = RequestConfig;
1 change: 1 addition & 0 deletions packages/las-sdk-core/src/types/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { JSONObject, OptionsOmit, PaginationOptions, RequestConfig } from './com

export type Agent = {
/* Id */
id: string;
agentId: string;
/* Attributes */
createdBy: string;
Expand Down
1 change: 1 addition & 0 deletions packages/las-sdk-core/src/types/agentRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type AgentRunEvent = {

export type AgentRun = {
/* Id */
id: string;
agentId: string;
runId: string;
/* Attributes */
Expand Down
1 change: 0 additions & 1 deletion packages/las-sdk-core/src/types/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export type Document = {
fileUrl: string;
metadata: JSONObject;
name?: string | null;
agentId?: string | null;
agentRunId?: string | null;
retentionInDays: number;
updatedBy?: string | null;
Expand Down
1 change: 1 addition & 0 deletions packages/las-sdk-core/src/types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type Runtime = (typeof RuntimeValues)[number];

export type Function = {
/* Id */
id: string;
functionId: string;
/* Attributes */
fileUrl: string;
Expand Down
4 changes: 2 additions & 2 deletions packages/las-sdk-core/src/types/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type Trigger = (typeof TriggerValues)[number];

export type Hook = {
/* Id */
id: string;
hookId: string;
/* Attributes */
config: JSONObject;
Expand All @@ -22,7 +23,6 @@ export type Hook = {
functionId?: string | null;
metadata: JSONObject;
name?: string | null;
agentId: string;
trigger: Trigger;
trueActionId?: string | null;
updatedBy?: string | null;
Expand All @@ -38,6 +38,6 @@ export type ListHooksOptions = RequestConfig & PaginationOptions;
export type GetHookOptions = RequestConfig;
export type CreateHookOptions = RequestConfig &
OptionsOmit<Hook, 'hookId'> &
Pick<Hook, 'functionId' | 'config' | 'agentId' | 'trigger'>;
Pick<Hook, 'functionId' | 'config' | 'trigger'>;
export type UpdateHookOptions = RequestConfig & OptionsOmit<Hook, 'hookId'>;
export type DeleteHookOptions = RequestConfig;
Loading
Loading