Skip to content

Commit

Permalink
feat(ui): support for ID token (#355)
Browse files Browse the repository at this point in the history
* feat(ui): support for ID token

* handle undefined oidc host

* submodule update
  • Loading branch information
glasstiger authored Nov 21, 2024
1 parent 46af9fb commit 0494e85
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 47 deletions.
20 changes: 6 additions & 14 deletions packages/browser-tests/cypress/integration/auth/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ describe("Auth - UI", () => {
"acl.basic.auth.realm.enabled": false,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
"acl.oidc.host": null,
"acl.oidc.port": null,
"acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
"acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
Expand All @@ -59,12 +57,10 @@ describe("Auth - OIDC", () => {
"acl.basic.auth.realm.enabled": false,
"acl.oidc.enabled": true,
"acl.oidc.client.id": "test",
"acl.oidc.host": "host",
"acl.oidc.port": 9999,
"acl.oidc.tls.enabled": true,
"acl.oidc.authorization.endpoint": "/auth",
"acl.oidc.token.endpoint": "/token",
"acl.oidc.authorization.endpoint": "https://host:9999/auth",
"acl.oidc.token.endpoint": "https://host:9999/token",
"acl.oidc.pkce.required": true,
"acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
Expand All @@ -86,12 +82,10 @@ describe("Auth - Basic", () => {
"acl.basic.auth.realm.enabled": true,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
"acl.oidc.host": null,
"acl.oidc.port": null,
"acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
"acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
Expand All @@ -111,12 +105,10 @@ describe("Auth - Disabled", () => {
"acl.basic.auth.realm.enabled": true,
"acl.oidc.enabled": false,
"acl.oidc.client.id": null,
"acl.oidc.host": null,
"acl.oidc.port": null,
"acl.oidc.tls.enabled": null,
"acl.oidc.authorization.endpoint": null,
"acl.oidc.token.endpoint": null,
"acl.oidc.pkce.required": null,
"acl.oidc.groups.encoded.in.token": false,
});
cy.visit(baseUrl);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-tests/questdb
Submodule questdb updated 499 files
2 changes: 2 additions & 0 deletions packages/web-console/src/modules/OAuth2/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export type AuthPayload = {
access_token: string
id_token: string
refresh_token: string
token_type: string
expires_in: number
expires_at?: string
groups_encoded_in_token?: boolean
}
30 changes: 18 additions & 12 deletions packages/web-console/src/modules/OAuth2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ type TokenPayload = Partial<{
refresh_token: string
}>

const getBaseURL = (config: Settings) => {
return `${config["acl.oidc.tls.enabled"] ? "https" : "http"}://${
config["acl.oidc.host"]
}:${config["acl.oidc.port"]}`
const getBaseURL = (settings: Settings) => {
// if there is no host in settings, no need to construct base URL at all
if (!settings["acl.oidc.host"]) {
return "";
}

// if there is host in settings, we are in legacy mode, and we should construct the base URL
return `${settings["acl.oidc.tls.enabled"] ? "https" : "http"}://${
settings["acl.oidc.host"]
}:${settings["acl.oidc.port"]}`
}

export const getAuthorisationURL = ({
config,
settings,
code_challenge = null,
login,
redirect_uri,
}: {
config: Settings
settings: Settings
code_challenge: string | null
login?: boolean
redirect_uri: string
}) => {
const params = {
client_id: config["acl.oidc.client.id"] || "",
client_id: settings["acl.oidc.client.id"] || "",
response_type: "code",
scope: config["acl.oidc.scope"] || "openid",
scope: settings["acl.oidc.scope"] || "openid",
redirect_uri,
}

Expand All @@ -43,8 +49,8 @@ export const getAuthorisationURL = ({
}

return (
getBaseURL(config) +
config["acl.oidc.authorization.endpoint"] +
getBaseURL(settings) +
settings["acl.oidc.authorization.endpoint"] +
"?" +
urlParams
)
Expand All @@ -70,5 +76,5 @@ export const getAuthToken = async (
)
}

export const hasUIAuth = (config: Settings) =>
config["acl.enabled"] && !config["acl.basic.auth.realm.enabled"]
export const hasUIAuth = (settings: Settings) =>
settings["acl.enabled"] && !settings["acl.basic.auth.realm.enabled"]
23 changes: 11 additions & 12 deletions packages/web-console/src/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,13 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
)
const [state, dispatch] = useReducer(reducer, initialState)

const setAuthToken = (tokenResponse: AuthPayload) => {
if (tokenResponse.access_token) {
const setAuthToken = (tokenResponse: AuthPayload, settings: Settings) => {
if (tokenResponse.access_token && tokenResponse.id_token) {
tokenResponse.groups_encoded_in_token = settings["acl.oidc.groups.encoded.in.token"]
tokenResponse.expires_at = getTokenExpirationDate(tokenResponse.expires_in).toString() // convert from the sec offset
setValue(
StoreKey.AUTH_PAYLOAD,
JSON.stringify({
...tokenResponse,
expires_at: getTokenExpirationDate(tokenResponse.expires_in), // convert from the sec offset
}),
JSON.stringify(tokenResponse),
)
// if the token payload does not contain the rolling refresh token, we'll keep the old one
if (tokenResponse.refresh_token) {
Expand Down Expand Up @@ -119,7 +118,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
client_id: settings["acl.oidc.client.id"],
})
const tokenResponse = await response.json()
setAuthToken(tokenResponse)
setAuthToken(tokenResponse, settings)
return tokenResponse
}

Expand Down Expand Up @@ -175,15 +174,15 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {

// User is authenticated already
if (authPayload !== "") {
const token = JSON.parse(authPayload)
const tokenResponse = JSON.parse(authPayload)
// Check if the token expired or is about to in 30 seconds
if (
new Date(token.expires_at).getTime() - Date.now() < 30000 &&
new Date(tokenResponse.expires_at).getTime() - Date.now() < 30000 &&
getValue(StoreKey.AUTH_REFRESH_TOKEN) !== ""
) {
await refreshAuthToken(settings)
} else {
setSessionData(token)
setSessionData(tokenResponse)
}
} else {
// User has just been redirected back from the OAuth2 provider and has the code
Expand All @@ -198,7 +197,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
redirect_uri: settings["acl.oidc.redirect.uri"] || window.location.origin + window.location.pathname,
})
const tokenResponse = await response.json()
setAuthToken(tokenResponse)
setAuthToken(tokenResponse, settings)
} catch (e) {
throw e
}
Expand Down Expand Up @@ -253,7 +252,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const code_verifier = generateCodeVerifier(settings)
const code_challenge = generateCodeChallenge(code_verifier)
window.location.href = getAuthorisationURL({
config: settings,
settings,
code_challenge,
login,
redirect_uri: settings["acl.oidc.redirect.uri"] || window.location.href,
Expand Down
8 changes: 4 additions & 4 deletions packages/web-console/src/providers/QuestProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const QuestProvider = ({ children }: PropsWithChildren<Props>) => {

const setupClient = async (sessionData: Partial<AuthPayload>) => {
questClient.setCommonHeaders({
Authorization: `Bearer ${sessionData.access_token}`,
Authorization: `Bearer ${sessionData.groups_encoded_in_token ? sessionData.id_token : sessionData.access_token}`,
})

questClient.refreshTokenMethod = () => {
Expand All @@ -101,11 +101,11 @@ export const QuestProvider = ({ children }: PropsWithChildren<Props>) => {
}, [sessionData])

useEffect(() => {
const token = getValue(StoreKey.REST_TOKEN)
const restToken = getValue(StoreKey.REST_TOKEN)
// User has provided the basic auth credentials
if (token) {
if (restToken) {
questClient.setCommonHeaders({
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${restToken}`,
})
void finishAuthCheck()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type Settings = Partial<{
"release.version": string
"acl.enabled": boolean
"acl.basic.auth.realm.enabled": boolean
"acl.oidc.groups.encoded.in.token": boolean
"acl.oidc.enabled": boolean
"acl.oidc.client.id": string
"acl.oidc.redirect.uri": string
Expand Down
4 changes: 2 additions & 2 deletions packages/web-console/src/store/Telemetry/epics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ export const getConfig: Epic<StoreAction, TelemetryAction, StoreShape> = (
? getValue(StoreKey.AUTH_PAYLOAD)
: "{}"
const token = JSON.parse(authPayload) as AuthPayload
if (token.access_token) {
if (token.access_token && token.id_token) {
quest.setCommonHeaders({
Authorization: `Bearer ${token.access_token}`,
Authorization: `Bearer ${token.groups_encoded_in_token ? token.id_token : token.access_token}`,
})
} else {
const restToken = getValue(StoreKey.REST_TOKEN)
Expand Down
4 changes: 2 additions & 2 deletions packages/web-console/src/utils/questdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,10 @@ export class Client {
if (Client.numOfPendingQueries === 0) {
clearInterval(interval)
const newToken = await this.refreshTokenMethod()
if (newToken.access_token) {
if (newToken.access_token && newToken.id_token) {
this.setCommonHeaders({
...this.commonHeaders,
Authorization: `Bearer ${newToken.access_token}`,
Authorization: `Bearer ${newToken.groups_encoded_in_token ? newToken.id_token : newToken.access_token}`,
})
}
Client.refreshTokenPending = false
Expand Down

0 comments on commit 0494e85

Please sign in to comment.