Skip to content

Commit

Permalink
Merge branch 'main' into nw_markdown_export
Browse files Browse the repository at this point in the history
  • Loading branch information
nwoolmer authored Feb 14, 2025
2 parents e397c0d + 3990acf commit 4ee855b
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/browser-tests/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = defineConfig({
screenshotOnRunFailure: false,
video: false,
baseUrl: baseUrl,
chromeWebSecurity: false, //if it is true, cypress does not allow redirects
viewportWidth: 1280,
viewportHeight: 720,
specPattern: "cypress/integration/**/*.spec.js",
Expand Down
18 changes: 18 additions & 0 deletions packages/browser-tests/cypress/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@ Cypress.Commands.add("loadConsoleWithAuth", (clearWarnings) => {
}
});

Cypress.Commands.add("loadConsoleAsAdminAndCreateSSOGroup", (group, externalGroup = undefined) => {
cy.loadConsoleWithAuth(true);
cy.executeSQL(`CREATE GROUP ${group} WITH EXTERNAL ALIAS ${externalGroup || group};`);
cy.executeSQL(`GRANT HTTP TO ${group};`);
cy.logout();
});

Cypress.Commands.add("logout", () => {
cy.getByDataHook("button-logout").click();
cy.getByDataHook("auth-login").should("be.visible");
});

Cypress.Commands.add("executeSQL", (sql) => {
cy.clearEditor();
cy.typeQuery(sql);
cy.clickRun();
});

Cypress.Commands.add("refreshSchema", () => {
// toggle between auto-refresh modes to trigger a schema refresh
cy.getByDataHook("schema-auto-refresh-button").click();
Expand Down
99 changes: 99 additions & 0 deletions packages/browser-tests/cypress/integration/enterprise/oidc.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/// <reference types="cypress" />

const contextPath = process.env.QDB_HTTP_CONTEXT_WEB_CONSOLE || ""
const baseUrl = `http://localhost:9999${contextPath}`;
const settingsUrl = `${baseUrl}/settings`;

const oidcProviderUrl = "http://localhost:9032";
const oidcAuthorizationCodeUrl = `${oidcProviderUrl}/authorization`;
const oidcTokenUrl = `${oidcProviderUrl}/token`;

const interceptSettings = (payload) => {
cy.intercept({ method: "GET", url: settingsUrl }, payload).as(
"settings"
);
};

const interceptAuthorizationCodeRequest = (redirectUrl) => {
cy.intercept("GET", `${oidcAuthorizationCodeUrl}?**`, (req) => {
req.redirect(redirectUrl);
}).as('authorizationCode');
};

const interceptTokenRequest = (payload) => {
cy.intercept({ method: "POST", url: oidcTokenUrl }, payload).as(
"tokens"
);
};

describe("OIDC authentication", () => {
before(() => {
// setup SSO group mappings
cy.loadConsoleAsAdminAndCreateSSOGroup("group1");
});

beforeEach(() => {
// load login page
interceptSettings({
"release.type": "EE",
"release.version": "1.2.3",
"acl.enabled": true,
"acl.basic.auth.realm.enabled": false,
"acl.oidc.enabled": true,
"acl.oidc.client.id": "client1",
"acl.oidc.authorization.endpoint": oidcAuthorizationCodeUrl,
"acl.oidc.token.endpoint": oidcTokenUrl,
"acl.oidc.pkce.required": true,
"acl.oidc.state.required": false,
"acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);

cy.wait("@settings");
cy.getByDataHook("auth-login").should("be.visible");
cy.getByDataHook("button-sso-login").should("be.visible");
cy.getEditor().should("not.exist");
});

it("should login via OIDC", () => {
interceptAuthorizationCodeRequest(`${baseUrl}?code=abcdefgh`);
cy.getByDataHook("button-sso-login").click();
cy.wait("@authorizationCode");

interceptTokenRequest({
"access_token": "gslpJtzmmi6RwaPSx0dYGD4tEkom",
"refresh_token": "FUuAAqMp6LSTKmkUd5uZuodhiE4Kr6M7Eyv",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6I",
"token_type": "Bearer",
"expires_in": 300
});
cy.wait("@tokens");
cy.getEditor().should("be.visible");

cy.executeSQL("select current_user();");
cy.getGridRow(0).should("contain", "user1");

cy.logout();
});

it("should force authentication if token expired, and there is no refresh token", () => {
interceptAuthorizationCodeRequest(`${baseUrl}?code=abcdefgh`);
cy.getByDataHook("button-sso-login").click();
cy.wait("@authorizationCode");

interceptTokenRequest({
"access_token": "gslpJtzmmi6RwaPSx0dYGD4tEkom",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6I",
"token_type": "Bearer",
"expires_in": 0
});
cy.wait("@tokens");
cy.getEditor().should("be.visible");

cy.reload();
cy.getByDataHook("button-log-in").should("be.visible");

cy.getByDataHook("button-log-in").click()
cy.getEditor().should("be.visible");
});
});
1 change: 1 addition & 0 deletions packages/browser-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"test": "cypress run --env failOnSnapshotDiff=false --env requireSnapshots=false --spec 'cypress/integration/console/*.spec.js'",
"test:auth": "cypress run --env failOnSnapshotDiff=false --env requireSnapshots=false --spec 'cypress/integration/auth/*.spec.js'",
"test:enterprise": "cypress run --env failOnSnapshotDiff=false --env requireSnapshots=false --spec 'cypress/integration/enterprise/*.spec.js'",
"test:update": "yarn run test --env updateSnapshots=true"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-tests/questdb
Submodule questdb updated 146 files
7 changes: 7 additions & 0 deletions packages/web-console/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ and this project adheres to
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## 0.7.5 - 2025.02.13

### Fixed

- Fix floating-point regex capturing words beginning with 'E' [#394](https://github.com/questdb/ui/pull/394)
- Prevent login loop when token expires without a valid refresh token [#395](https://github.com/questdb/ui/pull/395)

## 0.7.4 - 2025.02.05

### Added
Expand Down
2 changes: 1 addition & 1 deletion packages/web-console/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@questdb/web-console",
"version": "0.7.4",
"version": "0.7.5",
"license": "Apache-2.0",
"description": "QuestDB Console",
"files": [
Expand Down
10 changes: 10 additions & 0 deletions packages/web-console/serve-dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ const server = http.createServer((req, res) => {
})

req.pipe(proxyReq, { end: true })
} else if (
reqPathName.startsWith("/userinfo")
) {
res.writeHead(200, { 'Content-Type': 'application/json' })
// TODO: should be able to set the response from the test
// add something like /setUserInfo?info={sub: "user2", groups: "bla"}
res.end(JSON.stringify({
sub: "user1",
groups: ["group1", "group2"]
}))
} else {
// serve static files from /dist folder
const filePath = path.join(
Expand Down
1 change: 1 addition & 0 deletions packages/web-console/src/components/TopBar/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export const Toolbar = () => {
onClick={() => logout()}
prefixIcon={<LogoutCircle size="18px" />}
skin="secondary"
data-hook="button-logout"
>
Log out
</Button>
Expand Down
1 change: 1 addition & 0 deletions packages/web-console/src/modules/OAuth2/views/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const Error = ({
<Text color="foreground">{errorMessage}</Text>
{!basicAuthEnabled && (
<Button
data-hook="button-login-with-other-account"
skin="secondary"
prefixIcon={<User size="18px" />}
onClick={() => onLogout()}
Expand Down
5 changes: 4 additions & 1 deletion packages/web-console/src/modules/OAuth2/views/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,10 @@ export const Login = ({
validationSchema={schema}
>
<Form.Item name="username" label="User name">
<Form.Input name="username" placeholder={"johndoe"} />
<Form.Input
name="username"
placeholder={"johndoe"}
/>
</Form.Item>
<Form.Item name="password" label="Password">
<Form.Input
Expand Down
1 change: 1 addition & 0 deletions packages/web-console/src/modules/OAuth2/views/logout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const Logout = ({ onLogout }: { onLogout: () => void }) => (
<Box gap="1rem">
<Text color="foreground">You have been logged out.</Text>
<Button
data-hook="button-log-in"
prefixIcon={<LoginCircle size={18} />}
skin="secondary"
onClick={onLogout}
Expand Down
4 changes: 2 additions & 2 deletions packages/web-console/src/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
await refreshAuthToken(settings)
} else {
// if there is no refresh token, user has to re-authenticate to get fresh tokens
dispatch({ view: View.loggedOut })
logout(true)
}
} else {
setSessionData(tokenResponse)
Expand All @@ -193,7 +193,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
removeValue(StoreKey.OAUTH_STATE)
const stateParam = urlParams.get("state")
if (!stateParam || state !== stateParam) {
dispatch({ view: View.loggedOut })
logout(true)
return
}
}
Expand Down
4 changes: 2 additions & 2 deletions run_browser_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ yarn workspace browser-tests test:auth

read -p "Press any key to continue... " -n1 -s

# Switch authentication on
# Switch OSS authentication on
export QDB_HTTP_USER=admin
export QDB_HTTP_PASSWORD=quest

# Running tests which assume authentication is on
# Running tests which assume that OSS authentication is on
./tmp/questdb-*/bin/questdb.sh start -d tmp/dbroot
yarn workspace browser-tests test
./tmp/questdb-*/bin/questdb.sh stop
Expand Down
53 changes: 53 additions & 0 deletions run_ent_browser_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash -x

# Run it from the 'ui' directory as:
# ./run_ent_browser_tests.sh

# Cleanup
rm -rf packages/browser-tests/cypress/snapshots/*
rm -rf tmp/dbroot
rm -rf tmp/questdb-*

# Clone questdb-enterprise
git clone https://github.com/questdb/questdb-enterprise.git tmp/questdb-enterprise
cd tmp/questdb-enterprise || exit 1
git submodule init
git submodule update
cd ../..

# Build server
mvn clean package -e -f tmp/questdb-enterprise/pom.xml -DskipTests -P build-ent-binaries 2>&1

# Unpack server
tar xzf tmp/questdb-enterprise/questdb-ent/target/questdb-enterprise-*-rt-*.tar.gz -C tmp/
mkdir tmp/dbroot

# Build web console
yarn install --immutable --immutable-cache
yarn workspace @questdb/react-components run build
yarn workspace @questdb/web-console run build

# Start proxy
node packages/web-console/serve-dist.js &
PID1="$!"
echo "Proxy started, PID=$PID1"

# Switch dev mode on
export QDB_DEV_MODE_ENABLED=true

# OIDC config
export QDB_ACL_OIDC_ENABLED=true
export QDB_ACL_OIDC_TLS_ENABLED=false
export QDB_ACL_OIDC_GROUPS_CLAIM=groups
export QDB_ACL_OIDC_CLIENT_ID=clientId
export QDB_ACL_OIDC_HOST=localhost
export QDB_ACL_OIDC_PORT=9999
export QDB_ACL_OIDC_USERINFO_ENDPOINT=/userinfo

# Running tests which assume authentication is off
./tmp/questdb-*/bin/questdb.sh start -d tmp/dbroot
yarn workspace browser-tests test:enterprise
./tmp/questdb-*/bin/questdb.sh stop

# Stop proxy
kill -SIGTERM $PID1

0 comments on commit 4ee855b

Please sign in to comment.