WIP
This commit is contained in:
parent
ae400722ba
commit
896ca59ace
@ -24,17 +24,17 @@ modify_table = ["Task", ""]
|
|||||||
modify_values = [["title", "'$title'"], ["description", "'$description'"], ["completed", "$completed"], ["list_id", "'$list.$id'"]]
|
modify_values = [["title", "'$title'"], ["description", "'$description'"], ["completed", "$completed"], ["list_id", "'$list.$id'"]]
|
||||||
aggregate_final_json_result = true
|
aggregate_final_json_result = true
|
||||||
|
|
||||||
[resolvers.getUser]
|
[resolvers.createUser]
|
||||||
operation_name = "getUser"
|
operation_name = "createUser"
|
||||||
|
|
||||||
[resolvers.getUser.resolver]
|
[resolvers.createUser.resolver]
|
||||||
command_type = "SQLSelect"
|
command_type = "SQLInsert"
|
||||||
columns = []
|
columns = []
|
||||||
tables = [["User", ""]]
|
tables = []
|
||||||
where_clauses = [["User", "username", "= '$username'"]]
|
where_clauses = []
|
||||||
join_clauses = []
|
join_clauses = []
|
||||||
modify_table = ["", ""]
|
modify_table = ["User", ""]
|
||||||
modify_values = []
|
modify_values = [["username", "'$username'"], ["name", "'$name'"]]
|
||||||
aggregate_final_json_result = true
|
aggregate_final_json_result = true
|
||||||
|
|
||||||
[resolvers.createList]
|
[resolvers.createList]
|
||||||
@ -62,3 +62,16 @@ join_clauses = []
|
|||||||
modify_table = ["Task", ""]
|
modify_table = ["Task", ""]
|
||||||
modify_values = [["title", "'$title'"], ["description", "'$description'"], ["completed", "$completed"], ["list_id", "'$list.$id'"], ["user_username", "'$user.$username'"]]
|
modify_values = [["title", "'$title'"], ["description", "'$description'"], ["completed", "$completed"], ["list_id", "'$list.$id'"], ["user_username", "'$user.$username'"]]
|
||||||
aggregate_final_json_result = true
|
aggregate_final_json_result = true
|
||||||
|
|
||||||
|
[resolvers.getUser]
|
||||||
|
operation_name = "getUser"
|
||||||
|
|
||||||
|
[resolvers.getUser.resolver]
|
||||||
|
command_type = "SQLSelect"
|
||||||
|
columns = []
|
||||||
|
tables = [["User", ""]]
|
||||||
|
where_clauses = [["User", "username", "= '$username'"]]
|
||||||
|
join_clauses = []
|
||||||
|
modify_table = ["", ""]
|
||||||
|
modify_values = []
|
||||||
|
aggregate_final_json_result = true
|
||||||
|
@ -4,7 +4,7 @@ log_level = "trace"
|
|||||||
|
|
||||||
[graphql]
|
[graphql]
|
||||||
# path and file name to GraphQL schema file
|
# path and file name to GraphQL schema file
|
||||||
schema_file = "todo5_schema.graphql"
|
schema_file = "todo_schema.graphql"
|
||||||
|
|
||||||
[proxy]
|
[proxy]
|
||||||
# host name or IP of basebox DB proxy
|
# host name or IP of basebox DB proxy
|
||||||
|
3
bbconf/compile_schema.sh
Executable file
3
bbconf/compile_schema.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Run basebox installer from the samples/toodo/bbconf directory
|
||||||
|
cargo run --manifest-path ../../../installer/Cargo.toml -- -c install-config.toml
|
@ -20,7 +20,7 @@ acc_aud = "account"
|
|||||||
|
|
||||||
[graphql]
|
[graphql]
|
||||||
# path and file name to GraphQL schema file
|
# path and file name to GraphQL schema file
|
||||||
schema_file = "todo5_schema.graphql"
|
schema_file = "todo_schema.graphql"
|
||||||
# Path and file name of the resolver map file
|
# Path and file name of the resolver map file
|
||||||
resolver_map_file = "bb_todo_resolvers.toml"
|
resolver_map_file = "bb_todo_resolvers.toml"
|
||||||
# Path and file name of the type map file
|
# Path and file name of the type map file
|
||||||
|
18
bbconf/install-config.toml
Normal file
18
bbconf/install-config.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Sample toml file; for testing only
|
||||||
|
|
||||||
|
[generic]
|
||||||
|
# the name of the project
|
||||||
|
project_name = "bb_todo"
|
||||||
|
# the folder where the generated files will be placed. If not specifed, the project name will be
|
||||||
|
# used to create a folder by that name in the current directory. Note that the folder must not exist
|
||||||
|
# yet in order to make ensure that an existing installation will not be overwritten by accident.
|
||||||
|
output = "output"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
# log level; can be off, error, warn, info, debug, trace
|
||||||
|
log_level = "info"
|
||||||
|
|
||||||
|
[graphql]
|
||||||
|
schema = "todo_schema.graphql"
|
||||||
|
|
||||||
|
[database]
|
@ -44,6 +44,11 @@ type Query {
|
|||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
|
||||||
|
createUser(
|
||||||
|
username: String!,
|
||||||
|
name: String!
|
||||||
|
): User @bb_resolver(_type: insert, _object: User, _fields: { username: "$username", name: "$name" })
|
||||||
|
|
||||||
createList(
|
createList(
|
||||||
title: String!
|
title: String!
|
||||||
user: User! # username needs to be specified as it's non-nullable
|
user: User! # username needs to be specified as it's non-nullable
|
@ -1,3 +1,9 @@
|
|||||||
|
<!--
|
||||||
|
Component that dispalys authentication related errors.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
|
-->
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div id="error-message">
|
<div id="error-message">
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<!--
|
<!--
|
||||||
Dummy component that handles OAuth login callback requests.
|
Dummy component that handles OAuth login callback requests.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
<!--
|
||||||
|
About component.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
|
-->
|
||||||
<script setup>
|
<script setup>
|
||||||
import AboutItem from './AboutItem.vue'
|
import AboutItem from './AboutItem.vue'
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||||
|
71
src/components/TodoRoot.vue
Normal file
71
src/components/TodoRoot.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!--
|
||||||
|
Root component for the Todo app that is displayed if the user is logged in.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {gqlQuery} from "../util/net";
|
||||||
|
import {store} from "../store";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TodoRoot",
|
||||||
|
|
||||||
|
/** Current state of the component */
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
/* array of todo lists currently known */
|
||||||
|
lists: [],
|
||||||
|
/* the name of the list currently being shown */
|
||||||
|
currentList: null,
|
||||||
|
/* tasks of the current list */
|
||||||
|
tasks: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mounted lifecycle hook.
|
||||||
|
*/
|
||||||
|
mounted() {
|
||||||
|
if (!store.session) {
|
||||||
|
console.error("TodoRoot component must not be loaded if user is not logged in.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load user info and his/her lists and todos */
|
||||||
|
gqlQuery(`query {
|
||||||
|
getUser(username: "${store.session.userName}") {
|
||||||
|
name
|
||||||
|
lists {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
tasks {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
list {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`).then(rspJson => {
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,3 +1,9 @@
|
|||||||
|
<!--
|
||||||
|
Welcome component.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
|
-->
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import { store} from "../store";
|
import { store} from "../store";
|
||||||
|
@ -55,7 +55,7 @@ router.beforeEach(async (to, from) => {
|
|||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
/* redirect to error component */
|
/* redirect to error component */
|
||||||
console.error("Failed to get session token: " + response.statusText);
|
console.error("Failed to complete login/get session data: " + response.statusText);
|
||||||
return {
|
return {
|
||||||
name: 'oauth-error',
|
name: 'oauth-error',
|
||||||
query: {
|
query: {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Network related functions.
|
* Network related functions.
|
||||||
*
|
*
|
||||||
* markus.thielen@basebox.health
|
* Part of the basebox sample Todo app.
|
||||||
|
* https://basebox.tech
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {store} from "../store";
|
import {store} from "../store";
|
||||||
@ -158,7 +159,8 @@ export function gqlQuery(query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode object as a query string
|
* Encode simple, unnested objects (dicts) as a query string
|
||||||
|
*
|
||||||
* @param obj - a JavaScript object to encode
|
* @param obj - a JavaScript object to encode
|
||||||
* @returns {string} query string, e.g. "parm1=778&read=all"
|
* @returns {string} query string, e.g. "parm1=778&read=all"
|
||||||
*/
|
*/
|
||||||
@ -170,3 +172,4 @@ export function objectToQueryString(obj) {
|
|||||||
}
|
}
|
||||||
return str.join("&");
|
return str.join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
90
src/util/oauth.js
Normal file
90
src/util/oauth.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* OAuth specific functions.
|
||||||
|
*
|
||||||
|
* Part of the basebox sample Todo app.
|
||||||
|
* https://basebox.tech
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {store} from "../store";
|
||||||
|
import {gqlQuery} from "./net";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OAuth callback and complete login process.
|
||||||
|
*
|
||||||
|
* After the user enters her/his credentials at the IdP (Keycloak) login form, she/he
|
||||||
|
* gets redirected to a client URL; this function handles this request.
|
||||||
|
*
|
||||||
|
* This function passes the query string received with the callback to the basebox broker,
|
||||||
|
* which responds with a session information struct (JSON) like this:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "token":"47caa9df-ac89-45a8-8b22-9c0925d6aed0",
|
||||||
|
* "username":"tester",
|
||||||
|
* "first_name":"Fred",
|
||||||
|
* "last_name":"Feuerstein",
|
||||||
|
* "roles":[
|
||||||
|
* "/user"
|
||||||
|
* ],
|
||||||
|
* "claims":{
|
||||||
|
* "groups":[
|
||||||
|
* "/user"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* The session is then stored in app state.
|
||||||
|
*
|
||||||
|
* Then, this function checks if the user is known in the database; since we use Keycloak/OpenID Connect
|
||||||
|
* for user management, the user might be available in Keycloak, but not in the database. So if the user
|
||||||
|
* is not there, it is created.
|
||||||
|
*
|
||||||
|
* @param queryString
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws Error if the session data cannot be retrieved.
|
||||||
|
*/
|
||||||
|
export async function oauthCallbackHandler(queryString) {
|
||||||
|
|
||||||
|
fetch(`${store.baseboxHost}/oauth/complete-login?${queryString}`, {
|
||||||
|
method: "POST",
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to get session data: " + response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store session data */
|
||||||
|
response.json(
|
||||||
|
).then(rspJson => {
|
||||||
|
store.session = rspJson;
|
||||||
|
store.userName = store.session.first_name ? store.session.first_name : store.session.username;
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(e => {
|
||||||
|
throw new Error("Failed to get session data: " + e.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the user on the broker/todo database.
|
||||||
|
*
|
||||||
|
* Since user management is done by Keycloak, which uses its own user database, we must
|
||||||
|
* make sure that out user is known in the app database (basebox broker). After successful
|
||||||
|
* login, this function must be called to do that.
|
||||||
|
*
|
||||||
|
* @param username unique username of new user
|
||||||
|
* @param firstName first name of new user
|
||||||
|
* @param lastName last name of new user
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function createUser(username, firstName, lastName) {
|
||||||
|
|
||||||
|
gqlQuery(`query {
|
||||||
|
createUser(
|
||||||
|
username: "${username}",
|
||||||
|
name: "${firstName} ${lastName}"
|
||||||
|
)`
|
||||||
|
).catch(err => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,13 @@
|
|||||||
|
<!--
|
||||||
|
Home view.
|
||||||
|
|
||||||
|
Part of the basebox sample Todo app.
|
||||||
|
https://basebox.tech
|
||||||
|
-->
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { store } from "../store";
|
import { store } from "../store";
|
||||||
|
import TodoRoot from "../components/TodoRoot.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a login.
|
* Perform a login.
|
||||||
@ -13,11 +20,15 @@ function login() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
<!-- Force user to log in before he/she can see tasks. -->
|
<!-- Force user to log in before he/she can see tasks. -->
|
||||||
<div v-if="!store.session" id="login-prompt">
|
<div v-if="!store.session" id="login-prompt">
|
||||||
<p>Your are currently not logged in.</p>
|
<p>Your are currently not logged in.</p>
|
||||||
<button class="btn btn-primary" @click="login" type="button">Login</button>
|
<button class="btn btn-primary" @click="login" type="button">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TodoRoot v-if="store.session" />
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user