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
57 changes: 45 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import { Firebolt } from 'firebolt-sdk'
const firebolt = Firebolt();

const connection = await firebolt.connect({
username: process.env.FIREBOLT_USERNAME,
password: process.env.FIREBOLT_PASSWORD,
auth: {
username: process.env.FIREBOLT_USERNAME,
password: process.env.FIREBOLT_PASSWORD,
},
database: process.env.FIREBOLT_DATABASE,
engineName: process.env.FIREBOLT_ENGINE_NAME
});
Expand Down Expand Up @@ -62,6 +64,7 @@ console.log(rows)
* <a href="#create-connection">Create connection</a>
* <a href="#connectionoptions">ConnectionOptions</a>
* <a href="#accesstoken">AccessToken</a>
* <a href="#serviceaccount">Service account</a>
* <a href="#enginename">engineName</a>
* <a href="#test-connection">Test connection</a>
* <a href="#engine-url">Engine URL</a>
Expand Down Expand Up @@ -112,10 +115,22 @@ const connection = await firebolt.connect(connectionOptions);
<a id="connectionoptions"></a>
#### ConnectionOptions
```typescript
type UsernamePasswordAuth = {
username: string;
password: string;
};

type AccessTokenAuth = {
accessToken: string;
};

type ServiceAccountAuth = {
client_id: string;
client_secret: string;
};

type ConnectionOptions = {
username?: string;
password?: string;
accessToken?: string;
auth: UsernamePasswordAuth | AccessTokenAuth | ServiceAccountAuth;
database: string;
engineName?: string;
engineEndpoint?: string;
Expand All @@ -137,13 +152,30 @@ and pass accessToken when creating the connection

```typescript
const connection = await firebolt.connect({
accessToken: "access_token",
auth: {
accessToken: "access_token",
},
engineName: 'engine_name',
account: 'account_name',
database: 'database',
});
```

<a id="serviceaccount"></a>
#### Service account
Instead of passing username/password, you can also use service account

```typescript
const connection = await firebolt.connect({
auth: {
client_id: 'b1c4918c-e07e-4ab2-868b-9ae84f208d26';
client_secret: 'secret';
},
engineName: 'engine_name',
account: 'account_name',
database: 'database',
});
```
<a id="test-connection"></a>
### Test connection
TODO: write motivation
Expand Down Expand Up @@ -430,14 +462,15 @@ providing only auth credentials
```typescript
import { FireboltResourceManager } from 'firebolt-sdk'

const authOptions = {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string,
const resourceManager = FireboltResourceManager();
await resourceManager.authenticate({
auth: {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string,
},
account: process.env.ACCOUNT_NAME as string
};
});

const resourceManager = FireboltResourceManager();
await resourceManager.authenticate(authOptions);
const engine = await resourceManager.engine.getByName(
process.env.FIREBOLT_ENGINE_NAME as string
);
Expand Down
78 changes: 68 additions & 10 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { LOGIN, REFRESH } from "../common/api";
import { Context, AuthOptions } from "../types";
import { LOGIN, SERVICE_ACCOUNT_LOGIN, REFRESH } from "../common/api";
import {
Context,
ConnectionOptions,
ServiceAccountAuth,
UsernamePasswordAuth,
AccessTokenAuth
} from "../types";

type Login = {
access_token: string;
Expand All @@ -8,12 +14,12 @@ type Login = {

export class Authenticator {
context: Context;
options: AuthOptions;
options: ConnectionOptions;

accessToken?: string;
refreshToken?: string;

constructor(context: Context, options: AuthOptions) {
constructor(context: Context, options: ConnectionOptions) {
context.httpClient.authenticator = this;
this.context = context;
this.options = options;
Expand Down Expand Up @@ -53,13 +59,14 @@ export class Authenticator {
}
}

async authenticate() {
authenticateWithToken(auth: AccessTokenAuth) {
const { accessToken } = auth;
this.accessToken = accessToken;
}

async authenticateWithPassword(auth: UsernamePasswordAuth) {
const { httpClient, apiEndpoint } = this.context;
const { username, password, accessToken } = this.options;
if (accessToken) {
this.accessToken = accessToken;
return;
}
const { username, password } = auth;
const url = `${apiEndpoint}/${LOGIN}`;
const body = JSON.stringify({
username,
Expand All @@ -78,4 +85,55 @@ export class Authenticator {
this.accessToken = access_token;
this.refreshToken = refresh_token;
}

async authenticateServiceAccount(auth: ServiceAccountAuth) {
const { httpClient, apiEndpoint } = this.context;
const { client_id, client_secret } = auth;

const params = new URLSearchParams({
client_id,
client_secret,
grant_type: "client_credentials"
});
const url = `${apiEndpoint}/${SERVICE_ACCOUNT_LOGIN}`;

this.accessToken = undefined;

const { access_token } = await httpClient
.request<{ access_token: string }>("POST", url, {
retry: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params
})
.ready();

this.accessToken = access_token;
}

async authenticate() {
const options = this.options.auth || this.options;

if ((options as AccessTokenAuth).accessToken) {
this.authenticateWithToken(options as AccessTokenAuth);
return;
}
if (
(options as UsernamePasswordAuth).username &&
(options as UsernamePasswordAuth).password
) {
await this.authenticateWithPassword(options as UsernamePasswordAuth);
return;
}
if (
(options as ServiceAccountAuth).client_id &&
(options as ServiceAccountAuth).client_secret
) {
await this.authenticateServiceAccount(options as ServiceAccountAuth);
return;
}

throw new Error("Please provide valid auth credentials");
}
}
1 change: 1 addition & 0 deletions src/common/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const LOGIN = "auth/v1/login";
export const SERVICE_ACCOUNT_LOGIN = "auth/v1/token";
export const REFRESH = "auth/v1/refresh";

export const DATABASES = "core/v1/account/databases";
Expand Down
17 changes: 17 additions & 0 deletions src/common/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ConnectionOptions } from "../types";

export const MISSING_USERNAME = 404001;
export const MISSING_PASSWORD = 404002;
export const MISSING_DATABASE = 404003;
Expand Down Expand Up @@ -70,3 +72,18 @@ export class AuthenticationError extends Error {
this.message = message;
}
}

export const authDeprecationWarning = (options: ConnectionOptions) => {
if (!options.auth) {
console.error(`
username, password, accessToken fields are deprecated.
Please use auth object instead:
connectionOptions = {
auth: {
username: 'username',
password: 'password'
}
}
`);
}
};
8 changes: 2 additions & 6 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Connection } from "../connection";
import { Authenticator } from "../auth";
import { Context, ConnectionOptions, FireboltClientOptions } from "../types";
import { checkArgumentExists } from "../common/util";
import { MISSING_PASSWORD, MISSING_USERNAME } from "../common/errors";
import { authDeprecationWarning } from "../common/errors";
import { ResourceManager } from "../service";

export class FireboltCore {
Expand All @@ -17,10 +16,7 @@ export class FireboltCore {
}

checkConnectionOptions(connectionOptions: ConnectionOptions) {
if (!connectionOptions.accessToken) {
checkArgumentExists(connectionOptions.username, MISSING_USERNAME);
checkArgumentExists(connectionOptions.password, MISSING_PASSWORD);
}
authDeprecationWarning(connectionOptions);
}

async connect(connectionOptions: ConnectionOptions) {
Expand Down
4 changes: 3 additions & 1 deletion src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AccountService } from "./account";
import { Authenticator } from "../auth";
import { QueryFormatter } from "../formatter";
import { AuthOptions, Context } from "../types";
import { authDeprecationWarning } from "../common/errors";

export class ResourceManager {
private context: Context;
Expand All @@ -28,8 +29,9 @@ export class ResourceManager {
this.account = new AccountService(this.context);
}

async authenticate(options: AuthOptions & { account?: string }) {
async authenticate(options: { auth: AuthOptions; account?: string }) {
const { account } = options;
authDeprecationWarning(options);
const auth = new Authenticator(this.context, options);
await auth.authenticate();
await this.account.resolveAccountId(account);
Expand Down
26 changes: 20 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ export type AdditionalConnectionParameters = {
userClients?: ConnectorVersion[];
};

export type UsernamePasswordAuth = {
username: string;
password: string;
};

export type AccessTokenAuth = {
accessToken: string;
};

export type ServiceAccountAuth = {
client_id: string;
client_secret: string;
};

export type AuthOptions =
| UsernamePasswordAuth
| AccessTokenAuth
| ServiceAccountAuth;

export type ConnectionOptions = {
username?: string;
password?: string;
Expand All @@ -74,12 +93,7 @@ export type ConnectionOptions = {
engineEndpoint?: string;
additionalParameters?: AdditionalConnectionParameters;
account?: string;
};

export type AuthOptions = {
username?: string;
password?: string;
accessToken?: string;
auth?: AuthOptions;
};

export type FireboltClientOptions = {
Expand Down
6 changes: 4 additions & 2 deletions test/integration/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Firebolt } from "../../src/index";

const connectionOptions = {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string,
auth: {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string
},
database: process.env.FIREBOLT_DATABASE as string,
engineName: process.env.FIREBOLT_ENGINE_NAME as string,
account: process.env.FIREBOLT_ACCOUNT as string
Expand Down
37 changes: 37 additions & 0 deletions test/integration/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Firebolt } from "../../src/index";

const auth = {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string
};

const connectionOptions = {
database: process.env.FIREBOLT_DATABASE as string,
engineName: process.env.FIREBOLT_ENGINE_NAME as string,
account: process.env.FIREBOLT_ACCOUNT as string
};

jest.setTimeout(20000);

describe("auth", () => {
it("support new auth connection options", async () => {
const firebolt = Firebolt({
apiEndpoint: process.env.FIREBOLT_API_ENDPOINT as string
});

await firebolt.connect({
...connectionOptions,
auth
});
});
it("support old auth connection options", async () => {
const firebolt = Firebolt({
apiEndpoint: process.env.FIREBOLT_API_ENDPOINT as string
});

await firebolt.connect({
...connectionOptions,
...auth
});
});
});
6 changes: 4 additions & 2 deletions test/integration/database.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Firebolt } from "../../src/index";

const connectionOptions = {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string,
auth: {
username: process.env.FIREBOLT_USERNAME as string,
password: process.env.FIREBOLT_PASSWORD as string
},
database: process.env.FIREBOLT_DATABASE as string,
engineName: process.env.FIREBOLT_ENGINE_NAME as string
};
Expand Down
Loading