Skip to content

Commit

Permalink
Merge pull request #138 from dekart-xyz/google-auth-flow
Browse files Browse the repository at this point in the history
Google auth flow integration (part 1)
  • Loading branch information
delfrrr authored Aug 20, 2023
2 parents 41e1f47 + 0d3e9f0 commit 4846904
Show file tree
Hide file tree
Showing 38 changed files with 1,906 additions and 820 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ AWS_SECRET_ACCESS_KEY=
DEKART_IAP_JWT_AUD=
DEKART_REQUIRE_IAP=0

# google oauth2 flow (WIP, not fully implemented)
DEKART_REQUIRE_GOOGLE_OAUTH2=0
DEKART_GOOGLE_OAUTH_CLIENT_ID=
DEKART_GOOGLE_OAUTH_SECRET=


# Amazon OIDC
DEKART_REQUIRE_AMAZON_OIDC=0

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
require (
github.com/snowflakedb/gosnowflake v1.6.22
github.com/stretchr/testify v1.8.1
golang.org/x/oauth2 v0.9.0
)

require (
Expand Down Expand Up @@ -113,7 +114,6 @@ require (
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/oauth2 v0.9.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
Expand Down
19 changes: 19 additions & 0 deletions proto/dekart.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,32 @@ message GetEnvResponse {
TYPE_REQUIRE_AMAZON_OIDC = 7;
TYPE_REQUIRE_IAP = 8;
TYPE_DISABLE_USAGE_STATS = 9;
TYPE_REQUIRE_GOOGLE_OAUTH = 10;
}
Type type = 1;
string value = 2;
}
repeated Variable variables = 1;
}

// RedirectState is used to pass state between the server and the UI via redirect
message RedirectState {
string token_json = 1;
string error = 2;
}

message AuthState {
enum Action {
ACTION_UNSPECIFIED = 0;
ACTION_REQUEST_CODE = 1;
ACTION_REQUEST_TOKEN = 2;
}
Action action = 1;
string auth_url = 2;
string ui_url = 3;
string secret = 4;
}

message ArchiveReportRequest {
string report_id = 1;
bool archive = 2;
Expand Down
69 changes: 61 additions & 8 deletions src/client/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,65 @@ import {
Switch,
Route,
Redirect,
useParams
useParams,
useLocation
} from 'react-router-dom'
import ReportPage from './ReportPage'
import HomePage from './HomePage'
import { QuestionOutlined, WarningOutlined } from '@ant-design/icons'
import Result from 'antd/es/result'
import { useSelector, useDispatch } from 'react-redux'
import { getEnv } from './actions'
import { getUsage } from './actions/usage'
import { AuthState, RedirectState as DekartRedirectState } from '../proto/dekart_pb'
import { getEnv } from './actions/env'
import { setRedirectState } from './actions/redirectState'

// RedirectState reads states passed in the URL from the server
function RedirectState () {
const dispatch = useDispatch()
const url = new URL(window.location.href)
const params = new URLSearchParams(url.search)
let redirectState = null
if (params.has('redirect_state')) {
const redirectStateBase64 = params.get('redirect_state')
const redirectStateStr = atob(redirectStateBase64)
const redirectStateArr = [].map.call(redirectStateStr, x => x.charCodeAt(0))
const redirectStateBytes = new Uint8Array(redirectStateArr)
redirectState = DekartRedirectState.deserializeBinary(redirectStateBytes)
}
useEffect(() => {
if (redirectState) {
dispatch(setRedirectState(redirectState))
}
}, [redirectState, dispatch])
if (redirectState) {
params.delete('redirect_state')
url.search = params.toString()
return <Redirect to={`${url.pathname}${url.search}`} /> // apparently receives only pathname and search
}
return null
}

function AppRedirect () {
const httpErrorStatus = useSelector(state => state.httpErrorStatus)
const httpError = useSelector(state => state.httpError)
const { newReportId } = useSelector(state => state.reportStatus)
const location = useLocation()

if (httpError.status === 401 && httpError.doNotAuthenticate === false) {
const { REACT_APP_API_HOST } = process.env
const req = new URL('/api/v1/authenticate', REACT_APP_API_HOST || window.location.href)
const state = new AuthState()
state.setAuthUrl(req.href)
state.setUiUrl(window.location.href)
state.setAction(AuthState.Action.ACTION_REQUEST_CODE)
const stateBase64 = btoa(String.fromCharCode.apply(null, state.serializeBinary()))
req.searchParams.set('state', stateBase64)
window.location.href = req.href
return null
}

if (httpErrorStatus) {
return <Redirect to={`/${httpErrorStatus}`} />
if (httpError.status && location.pathname !== `/${httpError.status}`) {
return <Redirect to={`/${httpError.status}`} push />
}

if (newReportId) {
Expand All @@ -35,19 +78,30 @@ function RedirectToSource () {
}

export default function App () {
const errorMessage = useSelector(state => state.httpError.message)
const status = useSelector(state => state.httpError.status)
const env = useSelector(state => state.env)
const usage = useSelector(state => state.usage)
const dispatch = useDispatch()
useEffect(() => {
if (window.location.pathname.startsWith('/401')) {
// do not load env and usage on 401 page
return
}
if (status === 401) {
return
}
if (!env.loaded) {
dispatch(getEnv())
}
if (!usage.loaded) {
dispatch(getUsage())
}
})
}, [env, usage, dispatch, status])
return (
<Router>
<RedirectState />
<AppRedirect />
<Switch>
<Route exact path='/'>
<HomePage />
Expand All @@ -65,13 +119,12 @@ export default function App () {
<Result icon={<WarningOutlined />} title='400' subTitle='Bad Request' />
</Route>
<Route path='/401'>
<Result icon={<WarningOutlined />} title='401' subTitle='Unauthorized' />
<Result icon={<WarningOutlined />} title='401' subTitle={errorMessage || 'Unauthorized'} />
</Route>
<Route path='*'>
<Result icon={<QuestionOutlined />} title='404' subTitle='Page not found' />
</Route>
</Switch>
<AppRedirect />
</Router>
)
}
3 changes: 2 additions & 1 deletion src/client/Dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import Button from 'antd/es/button'
import styles from './Dataset.module.css'
import { useDispatch, useSelector } from 'react-redux'
import Query from './Query'
import { createQuery, createFile } from './actions'
import File from './File'
import { getDatasourceMeta } from './lib/datasource'
import { createQuery } from './actions/query'
import { createFile } from './actions/file'

function DatasetSelector ({ dataset }) {
const dispatch = useDispatch()
Expand Down
2 changes: 1 addition & 1 deletion src/client/DatasetSettingsModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Input from 'antd/es/input'
import { useDispatch, useSelector } from 'react-redux'
import { useState, useEffect } from 'react'
import getDatasetName from './lib/getDatasetName'
import { closeDatasetSettingsModal, removeDataset, updateDataset } from './actions'
import { closeDatasetSettingsModal, removeDataset, updateDataset } from './actions/dataset'

function ModalFooter ({ saving, setSaving, name, datasetId }) {
const dispatch = useDispatch()
Expand Down
2 changes: 1 addition & 1 deletion src/client/File.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Button from 'antd/es/button'
import { useState } from 'react'
import prettyBites from 'pretty-bytes'
import { useSelector, useDispatch } from 'react-redux'
import { uploadFile } from './actions'
import { uploadFile } from './actions/file'

function getFileExtensionName (type) {
switch (type) {
Expand Down
3 changes: 2 additions & 1 deletion src/client/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import Button from 'antd/es/button'
import Radio from 'antd/es/radio'
import Result from 'antd/es/result'
import Table from 'antd/es/table'
import { archiveReport, createReport, subscribeReports, testVersion, unsubscribeReports } from './actions'
import { useDispatch, useSelector } from 'react-redux'
import { PlusOutlined, FileSearchOutlined, GiftOutlined, UsergroupAddOutlined } from '@ant-design/icons'
import DataDocumentationLink from './DataDocumentationLink'
import { getRef } from './lib/ref'
import Switch from 'antd/es/switch'
import { archiveReport, subscribeReports, unsubscribeReports, createReport } from './actions/report'
import { testVersion } from './actions/version'

function Loading () {
return null
Expand Down
3 changes: 2 additions & 1 deletion src/client/Query.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { AutoSizer } from 'react-virtualized'
import Button from 'antd/es/button'
import styles from './Query.module.css'
import { useDispatch, useSelector } from 'react-redux'
import { cancelQuery, queryChanged, runQuery, showDataTable } from './actions'
import 'ace-builds/src-noconflict/mode-sql'
// import 'ace-builds/src-noconflict/theme-textmate'
import 'ace-builds/src-noconflict/theme-sqlserver'
Expand All @@ -15,6 +14,8 @@ import { SendOutlined, CheckCircleTwoTone, ExclamationCircleTwoTone, ClockCircle
import { Duration } from 'luxon'
import prettyBites from 'pretty-bytes'
import DataDocumentationLink from './DataDocumentationLink'
import { cancelQuery, queryChanged, runQuery } from './actions/query'
import { showDataTable } from './actions/showDataTable'

function CancelButton ({ query }) {
const dispatch = useDispatch()
Expand Down
2 changes: 1 addition & 1 deletion src/client/ReportHeaderButtons.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useHistory } from 'react-router'
import styles from './ReportHeaderButtons.module.css'
import Button from 'antd/es/button'
import { saveMap, forkReport } from './actions'
import { FundProjectionScreenOutlined, EditOutlined, ConsoleSqlOutlined, ForkOutlined } from '@ant-design/icons'
import { useDispatch, useSelector } from 'react-redux'
import ShareButton from './ShareButton'
import { forkReport, saveMap } from './actions/report'

function ForkButton ({ reportId, disabled, primary }) {
const dispatch = useDispatch()
Expand Down
6 changes: 4 additions & 2 deletions src/client/ReportPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { KeplerGl } from '@dekart-xyz/kepler.gl/dist/components'
import styles from './ReportPage.module.css'
import { AutoSizer } from 'react-virtualized'
import { useDispatch, useSelector } from 'react-redux'
import { closeReport, openReport, reportTitleChange, setActiveDataset, error, createDataset, openDatasetSettingsModal } from './actions'
import { EditOutlined, WarningFilled, MoreOutlined } from '@ant-design/icons'
import { Query as QueryType } from '../proto/dekart_pb'
import Tabs from 'antd/es/tabs'
Expand All @@ -18,6 +17,9 @@ import Dataset from './Dataset'
import { Resizable } from 're-resizable'
import DatasetSettingsModal from './DatasetSettingsModal'
import getDatasetName from './lib/getDatasetName'
import { createDataset, openDatasetSettingsModal, setActiveDataset } from './actions/dataset'
import { closeReport, openReport, reportTitleChange } from './actions/report'
import { setError } from './actions/message'

function TabIcon ({ query }) {
let iconColor = 'transparent'
Expand Down Expand Up @@ -224,7 +226,7 @@ function Kepler () {
<div className={styles.keplerBlock}>
<AutoSizer>
{({ height, width }) => (
<CatchKeplerError onError={(err) => dispatch(error(err))}>
<CatchKeplerError onError={(err) => dispatch(setError(err))}>
<KeplerGl
id='kepler'
mapboxApiAccessToken={env.variables.MAPBOX_TOKEN}
Expand Down
3 changes: 2 additions & 1 deletion src/client/ShareButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import Modal from 'antd/es/modal'
import { ExportOutlined, UsergroupAddOutlined, LinkOutlined, LockOutlined, InfoCircleOutlined, FileSearchOutlined } from '@ant-design/icons'
import { useState } from 'react'
import styles from './ShareButton.module.css'
import { copyUrlToClipboard, setDiscoverable } from './actions'
import { useDispatch, useSelector } from 'react-redux'
import Tooltip from 'antd/es/tooltip'
import { getRef } from './lib/ref'
import Switch from 'antd/es/switch'
import { toggleModal } from '@dekart-xyz/kepler.gl/dist/actions/ui-state-actions'
import { EXPORT_DATA_ID, EXPORT_IMAGE_ID, EXPORT_MAP_ID } from '@dekart-xyz/kepler.gl/dist/constants'
import Dropdown from 'antd/es/dropdown'
import { copyUrlToClipboard } from './actions/clipboard'
import { setDiscoverable } from './actions/report'

function CopyLinkButton () {
const dispatch = useDispatch()
Expand Down
31 changes: 14 additions & 17 deletions src/client/actions/dataset.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CreateDatasetRequest, RemoveDatasetRequest, UpdateDatasetRequest } from '../../proto/dekart_pb'
import { Dekart } from '../../proto/dekart_pb_service'
import { unary } from '../lib/grpc'
import { downloading, error, finishDownloading, success } from './message'
import { grpcCall } from './grpc'
import { downloading, setError, finishDownloading, success } from './message'
import { addDataToMap, toggleSidePanel, removeDataset as removeDatasetFromKepler } from '@dekart-xyz/kepler.gl/dist/actions'
import { processCsvData, processGeojson } from '@dekart-xyz/kepler.gl/dist/processors'
import { get } from '../lib/api'
Expand All @@ -13,7 +13,7 @@ export function createDataset (reportId) {
dispatch({ type: createDataset.name })
const request = new CreateDatasetRequest()
request.setReportId(reportId)
unary(Dekart.CreateDataset, request).catch(err => dispatch(error(err)))
dispatch(grpcCall(Dekart.CreateDataset, request))
}
}

Expand All @@ -38,11 +38,7 @@ export function updateDataset (datasetId, name) {
const request = new UpdateDatasetRequest()
request.setDatasetId(datasetId)
request.setName(name)
try {
await unary(Dekart.UpdateDataset, request)
} catch (err) {
dispatch(error(err))
}
dispatch(grpcCall(Dekart.UpdateDataset, request))
}
}

Expand All @@ -53,7 +49,7 @@ export function removeDataset (datasetId) {
// removed active query
const datasetsLeft = datasets.filter(q => q.id !== datasetId)
if (datasetsLeft.length === 0) {
dispatch(error(new Error('Cannot remove last dataset')))
dispatch(setError(new Error('Cannot remove last dataset')))
return
}
dispatch(setActiveDataset(datasetsLeft.id))
Expand All @@ -62,22 +58,23 @@ export function removeDataset (datasetId) {

const request = new RemoveDatasetRequest()
request.setDatasetId(datasetId)
try {
await unary(Dekart.RemoveDataset, request)
dispatch(grpcCall(Dekart.RemoveDataset, request, (err, res) => {
if (err) {
return err
}
dispatch(success('Dataset removed'))
} catch (err) {
dispatch(error(err))
}
}))
}
}

export function downloadDataset (dataset, sourceId, extension, prevDatasetsList) {
return async (dispatch, getState) => {
dispatch({ type: downloadDataset.name, dataset })
dispatch(downloading(dataset))
const { token } = getState()
let data
try {
const res = await get(`/dataset-source/${sourceId}.${extension}`)
const res = await get(`/dataset-source/${sourceId}.${extension}`, token)
if (extension === 'csv') {
const csv = await res.text()
data = processCsvData(csv)
Expand All @@ -86,7 +83,7 @@ export function downloadDataset (dataset, sourceId, extension, prevDatasetsList)
data = processGeojson(json)
}
} catch (err) {
dispatch(error(err))
dispatch(setError(err))
return
}
const { datasets, files, queries, keplerGl } = getState()
Expand Down Expand Up @@ -150,7 +147,7 @@ export function downloadDataset (dataset, sourceId, extension, prevDatasetsList)
}))
}
} catch (err) {
dispatch(error(
dispatch(setError(
new Error(`Failed to add data to map: ${err.message}`),
false
))
Expand Down
Loading

0 comments on commit 4846904

Please sign in to comment.