Skip to content

Commit

Permalink
Merge pull request #181 from Canner/feature/authenticator
Browse files Browse the repository at this point in the history
Feature: Canner PAT authenticator
  • Loading branch information
kokokuo committed Jun 13, 2023
2 parents 4a0e2d2 + 646b3d8 commit 8b1b1d7
Show file tree
Hide file tree
Showing 28 changed files with 582 additions and 84 deletions.
9 changes: 8 additions & 1 deletion labs/playground1/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ start: build test-data/moma.db ../../node_modules
@vulcan start

# build the required packages
build: pkg-core pkg-build pkg-serve pkg-catalog-server pkg-cli pkg-extension-driver-duckdb
build: pkg-core pkg-build pkg-serve pkg-catalog-server pkg-cli pkg-extension-driver-duckdb pkg-extension-authenticator-canner


# build for core pakge
Expand Down Expand Up @@ -49,6 +49,13 @@ pkg-extension-driver-duckdb: ../../node_modules
rm -rf ./labs/playground1/node_modules/@vulcan-sql/extension-driver-duckdb; \
cp -R ./dist/packages/extension-driver-duckdb ./labs/playground1/node_modules/@vulcan-sql

pkg-extension-authenticator-canner: ../../node_modules
@cd ../..; \
yarn nx build extension-authenticator-canner; \
mkdir -p ./labs/playground1/node_modules/@vulcan-sql; \
rm -rf ./labs/playground1/node_modules/@vulcan-sql/extension-authenticator-canner; \
cp -R ./dist/packages/extension-authenticator-canner ./labs/playground1/node_modules/@vulcan-sql

# build and install for cli pakge
pkg-cli: ../../node_modules
@cd ../..; \
Expand Down
4 changes: 4 additions & 0 deletions labs/playground1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ make
## Testing Data

After installation, you can find `artists.csv` and `artworks.csv` under folder `test-data`. They are the data we used for this playground. You can also access the data base via [DuckDB CLI](https://duckdb.org/docs/api/cli): `duckdb ./test-data/moma.db`

## Examples

We provide some examples in the `examples` folder to show how to configured your Vulcan API
18 changes: 18 additions & 0 deletions packages/extension-authenticator-canner/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
38 changes: 38 additions & 0 deletions packages/extension-authenticator-canner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# extension-authenticator-canner

This extension make Data API(VulcanSQL API) can integrate with [Canner Enterprise](https://cannerdata.com/product) and use Canner as a authenticate server

This extension let Data API request can be authenticated with [Canner PAT](https://docs.cannerdata.com/product/api_sdk/api_personal_access_token)

## Install

1. Install package

```sql
npm i @vulcan-sql/extension-authenticator-canner
```

2. Update `vulcan.yaml`, enable the extension and enable the `auth` configuration.

```yaml
auth:
enabled: true
# The extension-authenticator-canner and [build-in authenticator](https://vulcansql.com/docs/data-privacy/authn) can work at the same time
extensions:
canner-authenticator: '@vulcan-sql/extension-authenticator-canner'
```

3. Update `vulcan.yaml`, define your `canner-authenticator`
```yaml
canner-authenticator:
# To having the same config structure to the authenticator middleware, we
options:
canner-pat:
# your canner enterprise host
host: 'my-canner-host-dns'
# your canner enterprise post
post: 443
# indicate using http or https default is false
ssl: true
```
14 changes: 14 additions & 0 deletions packages/extension-authenticator-canner/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
displayName: 'extension-authenticator-canner',
preset: '../../jest.preset.ts',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/extension-authenticator-canner',
};
30 changes: 30 additions & 0 deletions packages/extension-authenticator-canner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@vulcan-sql/extension-authenticator-canner",
"description": "Canner Enterprise authenticator for Vulcan SQL",
"version": "0.4.0",
"type": "commonjs",
"publishConfig": {
"access": "public"
},
"keywords": [
"vulcan",
"vulcan-sql",
"data",
"sql",
"database",
"data-warehouse",
"data-lake",
"api-builder",
"postgres",
"pg"
],
"repository": {
"type": "git",
"url": "https://github.com/Canner/vulcan.git"
},
"license": "MIT",
"peerDependencies": {
"@vulcan-sql/core": "~0.4.0-0",
"@vulcan-sql/serve": "~0.4.0-0"
}
}
32 changes: 32 additions & 0 deletions packages/extension-authenticator-canner/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"root": "packages/extension-authenticator-canner",
"sourceRoot": "packages/extension-authenticator-canner/src",
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/extension-authenticator-canner",
"main": "packages/extension-authenticator-canner/src/index.ts",
"tsConfig": "packages/extension-authenticator-canner/tsconfig.lib.json",
"assets": ["packages/extension-authenticator-canner/*.md"]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/extension-authenticator-canner/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/extension-authenticator-canner"],
"options": {
"jestConfig": "packages/extension-authenticator-canner/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
3 changes: 3 additions & 0 deletions packages/extension-authenticator-canner/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { CannerPATAuthenticator } from './lib';

export default [CannerPATAuthenticator];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './pat';
120 changes: 120 additions & 0 deletions packages/extension-authenticator-canner/src/lib/authenticator/pat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
ConfigurationError,
VulcanExtensionId,
InternalError,
} from '@vulcan-sql/core';
import {
BaseAuthenticator,
KoaContext,
AuthStatus,
AuthResult,
} from '@vulcan-sql/serve';
import { isEmpty } from 'lodash';
import axios from 'axios';
import config from '../config';

export interface CannerPATOptions {
host: string;
port: number;
// default is false
ssl: boolean;
}

@VulcanExtensionId('canner-pat')
export class CannerPATAuthenticator extends BaseAuthenticator<CannerPATOptions> {
private options: CannerPATOptions = {} as CannerPATOptions;

public override async onActivate() {
this.options = this.getOptions() as CannerPATOptions;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async getTokenInfo(ctx: KoaContext): Promise<any> {
throw new InternalError(`canner-pat does not support token generate.`);
}

public async authCredential(context: KoaContext) {
const incorrect = {
status: AuthStatus.INDETERMINATE,
type: this.getExtensionId()!,
};
const authorize = context.request.headers['authorization'];
if (
// no need to check this.options because it a external extension,
// it must be configured correctly to be load into container and can be used in authenticate middleware
!authorize ||
!authorize.toLowerCase().startsWith(this.getExtensionId()!)
)
return incorrect;

if (isEmpty(this.options) || !this.options.host)
throw new ConfigurationError(
'please provide correct connection information to Canner Enterprise, including "host".'
);

// validate request auth token
const token = authorize.trim().split(' ')[1];

try {
return await this.validate(token);
} catch (err) {
return {
status: AuthStatus.FAIL,
type: this.getExtensionId()!,
message: (err as Error).message,
};
}
}

private async validate(token: string) {
const res = await this.fetchCannerUser(token);
const cannerUser = res.data.data?.userMe;
const { username, ...restAttrs } = cannerUser;
return {
status: AuthStatus.SUCCESS,
type: this.getExtensionId()!, // method name
user: {
name: username,
attr: restAttrs,
},
} as AuthResult;
}

private async fetchCannerUser(token: string) {
const graphqlUrl = this.getCannerUrl('/web/graphql');
try {
return await axios.post(
graphqlUrl,
{
operationName: 'UserMe',
variables: {},
query:
'query UserMe{userMe {accountRole attributes createdAt email groups {id name} lastName firstName username}}',
},
{
headers: {
Authorization: `Token ${token}`,
},
}
);
} catch (error: any) {
const message = error.response
? `response status: ${
error.response.status
}, response data: ${JSON.stringify(error.response.data)}`
: `remote server does not response. request ${error.toJSON()}}`;
throw new InternalError(
`Failed to fetch user info from canner server: ${message}`
);
}
}
private getCannerUrl(path = '/') {
const { host, port, ssl = false } = this.options;
if (config.isOnKubernetes)
return `http://${process.env['WEB_SERVICE_HOST']}${path}`; // for internal usage, we don't need to specify port
else {
const protocol = ssl ? 'https' : 'http';
return `${protocol}://${host}${port ? `:${port}` : ''}${path}`;
}
}
}
10 changes: 10 additions & 0 deletions packages/extension-authenticator-canner/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface IEnvConfig {
// indicates whether the extension is running in k8s
isOnKubernetes?: boolean;
}

const config: IEnvConfig = {
isOnKubernetes: Boolean(process.env['IS_ON_KUBERNETES']) || false,
};

export default config;
1 change: 1 addition & 0 deletions packages/extension-authenticator-canner/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './authenticator/pat';
Loading

0 comments on commit 8b1b1d7

Please sign in to comment.