Skip to content

Commit 32d385f

Browse files
committed
chore: fix
1 parent 566cdc8 commit 32d385f

File tree

14 files changed

+233
-44
lines changed

14 files changed

+233
-44
lines changed

packages/cubejs-api-gateway/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"typings": "dist/src/index.d.ts",
1616
"scripts": {
1717
"test": "npm run unit",
18-
"unit": "jest --coverage dist/test",
18+
"unit": "CUBE_JS_NATIVE_API_GATEWAY_INTERNAL=true jest --coverage dist/test",
1919
"build": "rm -rf dist && npm run tsc",
2020
"tsc": "tsc",
2121
"watch": "tsc -w",

packages/cubejs-api-gateway/src/gateway.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ class ApiGateway {
150150

151151
public readonly checkAuthSystemFn: PreparedCheckAuthFn;
152152

153-
protected readonly contextToApiScopesFn: ContextToApiScopesFn;
153+
public readonly contextToApiScopesFn: ContextToApiScopesFn;
154154

155-
protected readonly contextToApiScopesDefFn: ContextToApiScopesFn =
155+
public readonly contextToApiScopesDefFn: ContextToApiScopesFn =
156156
async () => ['graphql', 'meta', 'data'];
157157

158158
protected readonly requestLoggerMiddleware: RequestLoggerMiddlewareFn;
@@ -546,20 +546,24 @@ class ApiGateway {
546546
}
547547

548548
if (getEnv('nativeApiGateway')) {
549-
const proxyMiddleware = createProxyMiddleware<Request, Response>({
550-
target: `http://127.0.0.1:${this.sqlServer.getNativeGatewayPort()}/v2`,
551-
changeOrigin: true,
552-
});
553-
554-
app.use(
555-
`${this.basePath}/v2`,
556-
proxyMiddleware as any
557-
);
549+
this.enableNativeApiGateway(app);
558550
}
559551

560552
app.use(this.handleErrorMiddleware);
561553
}
562554

555+
protected enableNativeApiGateway(app: ExpressApplication) {
556+
const proxyMiddleware = createProxyMiddleware<Request, Response>({
557+
target: `http://127.0.0.1:${this.sqlServer.getNativeGatewayPort()}/v2`,
558+
changeOrigin: true,
559+
});
560+
561+
app.use(
562+
`${this.basePath}/v2`,
563+
proxyMiddleware as any
564+
);
565+
}
566+
563567
public initSubscriptionServer(sendMessage: WebSocketSendMessageFn) {
564568
return new SubscriptionServer(this, sendMessage, this.subscriptionStore, this.wsContextAcceptor);
565569
}

packages/cubejs-api-gateway/src/sql-server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ export class SQLServer {
107107
this.sqlInterfaceInstance = await registerInterface({
108108
gatewayPort: this.gatewayPort,
109109
pgPort: options.pgSqlPort,
110+
contextToApiScopes: async ({ securityContext }) => {
111+
return this.apiGateway.contextToApiScopesFn(
112+
securityContext,
113+
getEnv('defaultApiScope') || await this.apiGateway.contextToApiScopesDefFn()
114+
);
115+
},
110116
checkAuth: async ({ request, token }) => {
111117
const { securityContext } = await this.apiGateway.checkAuthFn(request, token);
112118

packages/cubejs-api-gateway/test/auth.test.ts

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,47 @@ import { AdapterApiMock, DataSourceStorageMock } from './mocks';
1010
import { RequestContext } from '../src/interfaces';
1111
import { generateAuthToken } from './utils';
1212

13+
class ApiGatewayOpenAPI extends ApiGateway {
14+
protected isRunning: Promise<void> | null = null;
15+
16+
public coerceForSqlQuery(query, context: RequestContext) {
17+
return super.coerceForSqlQuery(query, context);
18+
}
19+
20+
public async startSQLServer(): Promise<void> {
21+
if (this.isRunning) {
22+
return this.isRunning;
23+
}
24+
25+
this.isRunning = this.sqlServer.init({});
26+
27+
return this.isRunning;
28+
}
29+
30+
public async shutdownSQLServer(): Promise<void> {
31+
try {
32+
await this.sqlServer.shutdown('fast');
33+
} catch (error) {
34+
console.log(`Error while shutting down server: ${error}`);
35+
}
36+
37+
this.isRunning = null;
38+
}
39+
}
40+
1341
function createApiGateway(handler: RequestHandler, logger: () => any, options: Partial<ApiGatewayOptions>) {
1442
const adapterApi: any = new AdapterApiMock();
1543
const dataSourceStorage: any = new DataSourceStorageMock();
1644

17-
class ApiGatewayFake extends ApiGateway {
18-
public coerceForSqlQuery(query, context: RequestContext) {
19-
return super.coerceForSqlQuery(query, context);
20-
}
21-
45+
class ApiGatewayFake extends ApiGatewayOpenAPI {
2246
public initApp(app: ExpressApplication) {
2347
const userMiddlewares: RequestHandler[] = [
2448
this.checkAuth,
2549
this.requestContextMiddleware,
2650
];
2751

2852
app.get('/test-auth-fake', userMiddlewares, handler);
53+
this.enableNativeApiGateway(app);
2954

3055
app.use(this.handleErrorMiddleware);
3156
}
@@ -50,6 +75,61 @@ function createApiGateway(handler: RequestHandler, logger: () => any, options: P
5075
};
5176
}
5277

78+
describe('test authorization with native gateway', () => {
79+
const expectSecurityContext = (securityContext) => {
80+
expect(securityContext.uid).toEqual(5);
81+
expect(securityContext.iat).toBeDefined();
82+
expect(securityContext.exp).toBeDefined();
83+
};
84+
85+
let app: ExpressApplication;
86+
let apiGateway: ApiGatewayOpenAPI;
87+
88+
const handlerMock = jest.fn((req, res) => {
89+
expectSecurityContext(req.context.authInfo);
90+
expectSecurityContext(req.context.securityContext);
91+
92+
res.status(200).end();
93+
});
94+
const loggerMock = jest.fn(() => {
95+
//
96+
});
97+
98+
beforeAll(async () => {
99+
const result = createApiGateway(handlerMock, loggerMock, {});
100+
101+
app = result.app;
102+
apiGateway = result.apiGateway;
103+
104+
await result.apiGateway.startSQLServer();
105+
});
106+
107+
beforeEach(() => {
108+
handlerMock.mockClear();
109+
loggerMock.mockClear();
110+
});
111+
112+
afterAll(async () => {
113+
await apiGateway.shutdownSQLServer();
114+
});
115+
116+
fit('default authorization', async () => {
117+
const token = generateAuthToken({ uid: 5, });
118+
119+
await request(app)
120+
.get('/cubejs-api/v2/stream')
121+
.set('Authorization', `${token}`)
122+
.expect(501);
123+
124+
// No bad logs
125+
expect(loggerMock.mock.calls.length).toEqual(0);
126+
// We should not call js handler, request should go into rust code
127+
expect(handlerMock.mock.calls.length).toEqual(0);
128+
129+
await apiGateway.shutdownSQLServer();
130+
});
131+
});
132+
53133
describe('test authorization', () => {
54134
test('default authorization', async () => {
55135
const loggerMock = jest.fn(() => {

packages/cubejs-backend-native/js/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export interface CheckSQLAuthResponse {
3737
skipPasswordCheck?: boolean,
3838
}
3939

40+
export interface ContextToApiScopesPayload {
41+
securityContext: any,
42+
}
43+
44+
export type ContextToApiScopesResponse = string[];
45+
4046
export interface CheckAuthPayload {
4147
request: Request<undefined>,
4248
token: string,
@@ -97,6 +103,7 @@ export interface CanSwitchUserPayload {
97103

98104
export type SQLInterfaceOptions = {
99105
pgPort?: number,
106+
contextToApiScopes: (payload: ContextToApiScopesPayload) => ContextToApiScopesResponse | Promise<ContextToApiScopesResponse>,
100107
checkAuth: (payload: CheckAuthPayload) => CheckAuthResponse | Promise<CheckAuthResponse>,
101108
checkSqlAuth: (payload: CheckSQLAuthPayload) => CheckSQLAuthResponse | Promise<CheckSQLAuthResponse>,
102109
load: (payload: LoadPayload) => unknown | Promise<unknown>,
@@ -357,6 +364,10 @@ export const registerInterface = async (options: SQLInterfaceOptions): Promise<S
357364
throw new Error('Argument options must be an object');
358365
}
359366

367+
if (typeof options.contextToApiScopes !== 'function') {
368+
throw new Error('options.contextToApiScopes must be a function');
369+
}
370+
360371
if (typeof options.checkAuth !== 'function') {
361372
throw new Error('options.checkAuth must be a function');
362373
}
@@ -392,6 +403,7 @@ export const registerInterface = async (options: SQLInterfaceOptions): Promise<S
392403
const native = loadNative();
393404
return native.registerInterface({
394405
...options,
406+
contextToApiScopes: wrapNativeFunctionWithChannelCallback(options.contextToApiScopes),
395407
checkAuth: wrapNativeFunctionWithChannelCallback(options.checkAuth),
396408
checkSqlAuth: wrapNativeFunctionWithChannelCallback(options.checkSqlAuth),
397409
load: wrapNativeFunctionWithChannelCallback(options.load),

packages/cubejs-backend-native/src/auth.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ use crate::gateway::{
1717
GatewayAuthContext, GatewayAuthContextRef, GatewayAuthService, GatewayAuthenticateResponse,
1818
GatewayCheckAuthRequest,
1919
};
20+
use crate::gateway::auth_service::GatewayContextToApiScopesResponse;
2021

2122
#[derive(Debug)]
2223
pub struct NodeBridgeAuthService {
2324
channel: Arc<Channel>,
2425
check_auth: Arc<Root<JsFunction>>,
2526
check_sql_auth: Arc<Root<JsFunction>>,
27+
context_to_api_scopes: Arc<Root<JsFunction>>,
2628
}
2729

2830
pub struct NodeBridgeAuthServiceOptions {
2931
pub check_auth: Root<JsFunction>,
3032
pub check_sql_auth: Root<JsFunction>,
33+
pub context_to_api_scopes: Root<JsFunction>,
3134
}
3235

3336
impl NodeBridgeAuthService {
@@ -36,6 +39,7 @@ impl NodeBridgeAuthService {
3639
channel: Arc::new(channel),
3740
check_auth: Arc::new(options.check_auth),
3841
check_sql_auth: Arc::new(options.check_sql_auth),
42+
context_to_api_scopes: Arc::new(options.context_to_api_scopes),
3943
}
4044
}
4145
}
@@ -129,16 +133,23 @@ struct CheckAuthTransportResponse {
129133
}
130134

131135
#[derive(Debug)]
132-
pub struct NativeAuthContext {
136+
pub struct NativeGatewayAuthContext {
133137
pub security_context: Option<serde_json::Value>,
134138
}
135139

136-
impl GatewayAuthContext for NativeAuthContext {
140+
impl GatewayAuthContext for NativeGatewayAuthContext {
137141
fn as_any(&self) -> &dyn Any {
138142
self
139143
}
140144
}
141145

146+
#[derive(Debug, Serialize)]
147+
struct ContextToApiScopesTransportRequest<'ref_auth_context> {
148+
security_context: &'ref_auth_context Option<serde_json::Value>,
149+
}
150+
151+
type ContextToApiScopesTransportResponse = Vec<String>;
152+
142153
#[async_trait]
143154
impl GatewayAuthService for NodeBridgeAuthService {
144155
async fn authenticate(
@@ -167,17 +178,37 @@ impl GatewayAuthService for NodeBridgeAuthService {
167178
trace!("[auth] Request <- {:?}", response);
168179

169180
Ok(GatewayAuthenticateResponse {
170-
context: Arc::new(NativeAuthContext {
181+
context: Arc::new(NativeGatewayAuthContext {
171182
security_context: response.security_context,
172183
}),
173184
})
174185
}
175186

176187
async fn context_to_api_scopes(
177188
&self,
178-
auth_context: GatewayAuthContextRef,
179-
) -> Result<GatewayAuthenticateResponse, CubeError> {
180-
unimplemented!();
189+
auth_context: &GatewayAuthContextRef,
190+
) -> Result<GatewayContextToApiScopesResponse, CubeError> {
191+
trace!("[context_to_api_scopes] Request ->");
192+
193+
let native_auth = auth_context
194+
.as_any()
195+
.downcast_ref::<NativeGatewayAuthContext>()
196+
.expect("Unable to cast AuthContext to NativeGatewayAuthContext");
197+
198+
let extra = serde_json::to_string(&ContextToApiScopesTransportRequest {
199+
security_context: &native_auth.security_context,
200+
})?;
201+
let response: ContextToApiScopesTransportResponse = call_js_with_channel_as_callback(
202+
self.channel.clone(),
203+
self.context_to_api_scopes.clone(),
204+
Some(extra),
205+
)
206+
.await?;
207+
trace!("[context_to_api_scopes] Request <- {:?}", response);
208+
209+
Ok(GatewayContextToApiScopesResponse {
210+
scopes: response,
211+
})
181212
}
182213
}
183214

packages/cubejs-backend-native/src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl NodeCubeServices {
7474
.injector
7575
.get_service_typed::<dyn ApiGatewayServer>()
7676
.await;
77+
7778
gateway_server.stop_processing(shutdown_mode).await?;
7879
}
7980

packages/cubejs-backend-native/src/gateway/auth_service.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub struct GatewayCheckAuthRequest {
2222
pub(crate) protocol: String,
2323
}
2424

25+
#[derive(Debug)]
26+
pub struct GatewayContextToApiScopesResponse {
27+
pub scopes: Vec<String>,
28+
}
29+
2530
#[async_trait]
2631
pub trait GatewayAuthService: Send + Sync + Debug {
2732
async fn authenticate(
@@ -32,6 +37,6 @@ pub trait GatewayAuthService: Send + Sync + Debug {
3237

3338
async fn context_to_api_scopes(
3439
&self,
35-
auth_context: GatewayAuthContextRef,
36-
) -> Result<GatewayAuthenticateResponse, CubeError>;
40+
auth_context: &GatewayAuthContextRef,
41+
) -> Result<GatewayContextToApiScopesResponse, CubeError>;
3742
}

packages/cubejs-backend-native/src/gateway/handlers/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub async fn stream_handler_v2(
1818
Extension(auth): Extension<AuthExtension>,
1919
) -> Result<impl IntoResponse, HttpError> {
2020
gateway_state
21-
.assert_api_scope(auth.auth_context(), "stream")
21+
.assert_api_scope(auth.auth_context(), "data")
2222
.await?;
2323

2424
Ok((

0 commit comments

Comments
 (0)