BBIO-46 WIP

This commit is contained in:
Markus Thielen 2023-10-31 18:44:19 +01:00
parent 20de6a58e4
commit 2b7a127701
Signed by: markus
GPG Key ID: 3D4980D3EC9C8E26
12 changed files with 278 additions and 275 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
HOST=127.0.0.1

View File

@ -13,14 +13,14 @@ Let's assume you create a `basebox` folder in your home directory, change into i
cd ~
mkdir basebox
cd basebox
tar xzf basebox-version.tgz # adopt this command to the archive you downloaded
tar xzf basebox-x.x.x.tgz # adopt this command to the archive you downloaded
git clone --single-branch --depth 1 https://gitea.basebox.health/samples/vue-todo.git
```
The basebox archive will be extracted into a directory named `basebox-<version>`; for the scripts in `vue-todo/bbconf` to find the basebox binaries, please create a symbolic link to the basebox directory named `basebox`, e.g.
``` sh
ln -s basebox-0.1.1-beta.1 basebox
ln -s basebox-x.x.x basebox
```
If you follow these steps, the scripts in `bbconf` will work out of the box.
@ -30,18 +30,15 @@ If you follow these steps, the scripts in `bbconf` will work out of the box.
To install this app, you need
* npm, node.js
* A PostgreSQL server, preferably on the local host (simpler, but not suitable for production)
* basebox components (broker, dbproxy, bbc)
* Keycloak
* A PostgreSQL server
* basebox (broker, dbproxy, bbc extracted from the basebox archive)
* Keycloak or Auth0
### PostgreSQL Preparation
> You need a PostgreSQL database to run this app. You can read detailed instructions on how to
> install PostgreSQL and create a test database at our [PostgreSQL Primer](https://docs.basebox.io/getting-started/postgresql/) page.
The recommended way to run basebox is to have a dedicated Unix user for basebox that authenticates to PostgreSQL using Peer authentication. This way, no password for the DB user has to be stored anywhere.
However, to keep things simple for the test app, we create a local user that authenticates to the database server via password (md5).
Let's create a PostgreSQL user and database for the TODO app:
``` sh
@ -98,7 +95,7 @@ This will install the client app's dependencies.
### OpenID Connect
You can setup and or use your own OpenID Connect server; if you do so, you also have to update the config files `bbconf/broker-config.toml` and `bbconf/dbroxy-config.toml` accordingly.
You can setup and use your own OpenID Connect server; if you do so, you also have to update the config files `bbconf/broker-config.toml` and `bbconf/dbroxy-config.toml` accordingly.
If you just want to try things out, you are welcome to use our development Keycloak server. The config files are already setup to use it. There is a test user named `tester`, the password is `rantanplan`.
@ -149,6 +146,4 @@ This will start a node.js based HTTP test server that will host the client appli
```
**Important Note**: If the URL you are seeing uses *localhost*, you have to edit broker's config file accordingly. The value you have to change is `redirect_url`; replace "127.0.0.1" with "localhost".
Open your browser, go go the URL in the npm console, and enjoy.

View File

@ -1,5 +1,5 @@
--
-- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-10-30 16:54:43+01:00
-- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-10-31 15:13:34+01:00
--
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@ -1,147 +1,6 @@
#
# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-10-30 16:54:43+01:00
# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-10-31 15:13:34+01:00
#
[resolvers.deleteTask]
operation_name = "deleteTask"
[resolvers.deleteTask.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.deleteTask.resolver.QueryBuilder.command.where_clauses]]
table = "Task"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.createList]
operation_name = "createList"
[resolvers.createList.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createList.resolver.QueryBuilder.command]
table = "List"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createList.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createList.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.createTask]
operation_name = "createTask"
[resolvers.createTask.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "description"
value = "'$description'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "completed"
value = "'$completed'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "list_id"
value = "'$list.$id'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.deleteList]
operation_name = "deleteList"
[resolvers.deleteList.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteList.resolver.QueryBuilder.command]
table = "List"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.deleteList.resolver.QueryBuilder.command.where_clauses]]
table = "List"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers._bb_user_User]
operation_name = "_bb_user_User"
[resolvers._bb_user_User.resolver.InternalQueryBuilder]
command_type = "SQLSelect"
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command]
table = "User"
command_type = "SQLSelect"
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns]]
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns.Column]
table = "User"
column = "username"
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.where_clauses]]
table = "User"
column = ".ownerId"
condition_str = "= $1"
index = ""
[resolvers.getUser]
operation_name = "getUser"
[resolvers.getUser.resolver.QueryBuilder]
command_type = "SQLSelect"
[resolvers.getUser.resolver.QueryBuilder.command]
table = "User"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.getUser.resolver.QueryBuilder.command.where_clauses]]
table = "User"
column = "username"
condition_str = "= '$username'"
index = ""
[resolvers.updateTask]
operation_name = "updateTask"
@ -177,27 +36,39 @@ column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.createUser]
operation_name = "createUser"
[resolvers.createTask]
operation_name = "createTask"
[resolvers.createUser.resolver.QueryBuilder]
[resolvers.createTask.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createUser.resolver.QueryBuilder.command]
table = "User"
[resolvers.createTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createUser.resolver.QueryBuilder.command.modify_values]]
column = "username"
value = "'$username'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createUser.resolver.QueryBuilder.command.modify_values]]
column = "name"
value = "'$name'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "description"
value = "'$description'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "completed"
value = "'$completed'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "list_id"
value = "'$list.$id'"
[[resolvers.createTask.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.updateList]
operation_name = "updateList"
@ -221,3 +92,132 @@ table = "List"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.deleteTask]
operation_name = "deleteTask"
[resolvers.deleteTask.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteTask.resolver.QueryBuilder.command]
table = "Task"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.deleteTask.resolver.QueryBuilder.command.where_clauses]]
table = "Task"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.deleteList]
operation_name = "deleteList"
[resolvers.deleteList.resolver.QueryBuilder]
command_type = "SQLDelete"
[resolvers.deleteList.resolver.QueryBuilder.command]
table = "List"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.deleteList.resolver.QueryBuilder.command.where_clauses]]
table = "List"
column = "id"
condition_str = "= '$id'"
index = ""
[resolvers.createUser]
operation_name = "createUser"
[resolvers.createUser.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createUser.resolver.QueryBuilder.command]
table = "User"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createUser.resolver.QueryBuilder.command.modify_values]]
column = "username"
value = "'$username'"
[[resolvers.createUser.resolver.QueryBuilder.command.modify_values]]
column = "name"
value = "'$name'"
[resolvers._bb_user_User]
operation_name = "_bb_user_User"
[resolvers._bb_user_User.resolver.InternalQueryBuilder]
command_type = "SQLSelect"
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command]
table = "User"
command_type = "SQLSelect"
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns]]
[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.columns.Column]
table = "User"
column = "username"
[[resolvers._bb_user_User.resolver.InternalQueryBuilder.command.where_clauses]]
table = "User"
column = ".ownerId"
condition_str = "= $1"
index = ""
[resolvers.createList]
operation_name = "createList"
[resolvers.createList.resolver.QueryBuilder]
command_type = "SQLInsert"
[resolvers.createList.resolver.QueryBuilder.command]
table = "List"
command_type = "SQLSelect"
columns = []
nested_modify_tables = []
where_clauses = []
aggregate_result = true
[[resolvers.createList.resolver.QueryBuilder.command.modify_values]]
column = "title"
value = "'$title'"
[[resolvers.createList.resolver.QueryBuilder.command.modify_values]]
column = "user_username"
value = "'$user.$username'"
[resolvers.getUser]
operation_name = "getUser"
[resolvers.getUser.resolver.QueryBuilder]
command_type = "SQLSelect"
[resolvers.getUser.resolver.QueryBuilder.command]
table = "User"
command_type = "SQLSelect"
columns = []
modify_values = []
nested_modify_tables = []
aggregate_result = true
[[resolvers.getUser.resolver.QueryBuilder.command.where_clauses]]
table = "User"
column = "username"
condition_str = "= '$username'"
index = ""

View File

@ -1,50 +0,0 @@
[generic]
# log level; can be error, warn, info, debug, trace
log_level = "trace"
[graphql]
# path and file name to GraphQL schema file
schema_file = "todo_schema.graphql"
allow_introspection = true
[proxy]
# host name or IP of basebox DB proxy
host = "localhost"
port = 8081
# Whether to use http or https to connect to the proxy
tls = false
[server]
# Host name of the broker (GraphQL server)
host = "192.168.2.172"
# Port number; default is 80 for http, 443 for https
port = 8080
# number of HTTP server threads to spawn; default is one per CPU core
workers = 2
# Path and file name of TLS/SSL key file
# cert_key_file = "/path/to/key.pem"
# Path and file name of TLS certificate (chain) file
# cert_file = "/path/to/cert.pem"
[auth]
mode = "access-token"
# Base URL to the identity provider (OAuth2/OpenID Connect server, e.g. Keycloak)
iss = "https://basebox-test-1.eu.auth0.com/"
aud = "basebox-todo"
[business_logic_layer]
business_logic_layer_enabled = false
python_module_path = "/path/to/python/module"
python_module_name = "mymodule"
[business_logic_layer.pre_definition]
all = ["query"]
query = ["getExercises", "getExercise"]
mutation = ["createExercise", "updateExercise", "deleteExercise"]
fragment = []

View File

@ -31,52 +31,7 @@ workers = 2
# cert_file = "/path/to/cert.pem"
[auth]
mode = "client"
# OAuth2 client id
client_id = "5wl8hQV1thh07rScSoJ3aN56ETuXWprg"
# OAuth2 client secret
client_secret = "QlHMvIffKLRviCcSu_bPQcf8e4dc6WeS3BwZE1r1F-9R30AFoeYEwaOazAuFenI5"
# Base URL to the identity provider (OAuth2/OpenID Connect server, e.g. Keycloak)
idp_url = "https://basebox-test-1.eu.auth0.com"
# OpenID Connect scope; default is "openid profile email"
scope = "openid profile email"
# Fully qualified URL to the OAuth2 callback endpoint.
# After the user entered his/her credentials at the IdP's login form, the client will be redirected
# to this URL. When the client receives a request to this URL, it must send the request's query
# string to the broker's "openid_connect_path" set below.
redirect_url = "http://127.0.0.1:5173/oauth-callback"
# OpenID Connect login completion request path.
# The client must pass the query string from the call to "redirect_url" to this URL and gets
# a basebox session token in return.
openid_connect_path = "/oauth/complete-login"
# Path to the browser login URL.
# This path is where the basebox broker returns a 302 response that redirects the browser to
# the IdP login page; the target URL will contain all query parms needed to initiate an
# auth code flow login procedure, incl. CSRF protection tokens etc.
login_path = "/oauth/login"
# Logout path that allows explicit, immediate logouts.
# Simply POST to this URL with the session cookie or bearer token.
logout_path = "/oauth/logout"
# Set to true to get a user's additional claims from OAuth2
user_info_additional_claims_required = true
[business_logic_layer]
business_logic_layer_enabled = false
python_module_path = "/path/to/python/module"
python_module_name = "mymodule"
[business_logic_layer.pre_definition]
all = ["query"]
query = ["getExercises", "getExercise"]
mutation = ["createExercise", "updateExercise", "deleteExercise"]
fragment = []
# Contents of 'iss' field, usually the URL of the authetnication realm
iss = "https://basebox-test-1.eu.auth0.com/"
# Access token audience field
aud = "basebox-todo"

23
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.3.0-alpha1",
"bootstrap-icons": "^1.10.3",
"oidc-client-ts": "^2.4.0",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
@ -596,6 +597,11 @@
"fsevents": "~2.3.2"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"node_modules/csstype": {
"version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
@ -759,6 +765,11 @@
"node": ">=0.12.0"
}
},
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@ -787,6 +798,18 @@
"node": ">=0.10.0"
}
},
"node_modules/oidc-client-ts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz",
"integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==",
"dependencies": {
"crypto-js": "^4.2.0",
"jwt-decode": "^3.1.2"
},
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",

View File

@ -11,6 +11,7 @@
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.3.0-alpha1",
"bootstrap-icons": "^1.10.3",
"oidc-client-ts": "^2.4.0",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},

View File

@ -33,7 +33,7 @@ function dismissError() {
</button>
<!-- navigation items are shown only if logged in -->
<div v-if="store.session.token" class="collapse navbar-collapse" id="navbarSupportedContent">
<div v-if="$oidc.isAuthenticated" class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<RouterLink to="/" class="nav-link" active-class="active">Tasks</RouterLink>
@ -48,7 +48,7 @@ function dismissError() {
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="navbar-text">Hello, {{ store.userDisplayName }}!</li>
<li class="nav-item ms-2">
<button class="nav-link btn btn-sm btn-secondary" @click="logOut()">Logout</button>
<button class="nav-link btn btn-sm btn-secondary" @click="$oidc.signOut()">Logout</button>
</li>
</ul>
</div>

80
src/oidc/oidc-client.js Normal file
View File

@ -0,0 +1,80 @@
/**
* Configure oidc-client library.
*
*/
import Oidc from 'oidc-client';
Oidc.Log.logger = console;
Oidc.Log.level = (process.env.NODE_ENV === 'production') ? Oidc.Log.ERROR : Oidc.Log.DEBUG;
// OIDC configuration
let oidcProviderDomain = 'https://basebox-test-1.eu.auth0.com';
let clientId = '5wl8hQV1thh07rScSoJ3aN56ETuXWprg';
let scopes = "openid profile email"
let instance;
// OIDC Client
export const getOidcClient = () => {
if (instance) {
return instance;
}
instance = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore(),
authority: oidcProviderDomain,
client_id: clientId,
redirect_uri: window.location.origin + '/callback',
response_type: 'code',
scope: scopes,
post_logout_redirect_uri: window.location.origin + '/home?action=logout',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: false,
filterProtocolClaims: false,
loadUserInfo: true,
includeIdTokenInSilentRenew: false
});
instance.events.addAccessTokenExpiring(function() {
// eslint-disable-next-line no-console
console.log('access token expiring')
})
instance.events.addAccessTokenExpired(function() {
// eslint-disable-next-line no-console
console.log('access token expired')
})
instance.events.addSilentRenewError(function(err) {
// eslint-disable-next-line no-console
console.error('silent renew error', err)
})
instance.events.addUserLoaded(function(user) {
// eslint-disable-next-line no-console
console.log('user loaded', user)
})
instance.events.addUserSignedIn(function(user) {
// eslint-disable-next-line no-console
console.log('user signed in', user)
})
instance.events.addUserUnloaded(function() {
// eslint-disable-next-line no-console
console.log('user unloaded')
})
instance.events.addUserSignedOut(function() {
// eslint-disable-next-line no-console
console.log('user signed out')
})
instance.events.addUserSessionChanged(function() {
// eslint-disable-next-line no-console
console.log('user session changed')
})
return instance;
}

View File

@ -10,24 +10,17 @@ import { store } from "../store";
import {loggedIn} from "../store";
import TodoRoot from "../components/Todo.vue";
/**
* Perform a login.
*/
function login() {
location.href = `${store.baseboxHost}/oauth/login`;
}
</script>
<template>
<main>
<!-- Force user to log in before he/she can see tasks. -->
<div v-if="!loggedIn()" id="login-prompt">
<div v-if="!$oidc.isAuthenticated" id="login-prompt">
<h1>Welcome to basebox' TODO sample app!</h1>
<p class="mt-5">Your are currently not logged in.</p>
<button class="btn btn-primary" @click="login" type="button">Login</button>
<button class="btn btn-primary" @click="$oidc.signIn()" type="button">Login</button>
</div>
<TodoRoot v-if="loggedIn()" />

View File

@ -6,6 +6,10 @@ import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
host: '127.0.0.1',
port: 5167
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))