diff --git a/bbconf/bb_todo-datamodel.sql b/bbconf/bb_todo-datamodel.sql index b2ddeb9..46a3ab0 100644 --- a/bbconf/bb_todo-datamodel.sql +++ b/bbconf/bb_todo-datamodel.sql @@ -1,5 +1,5 @@ -- --- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-11-01 03:18:33+01:00 +-- Generated by basebox compiler (bbc) version 0.1.0-beta.23 at 2023-11-01 10:35:59+01:00 -- CREATE EXTENSION IF NOT EXISTS pgcrypto; @@ -23,6 +23,12 @@ CREATE TABLE "User" ( "name" VARCHAR ); +ALTER TABLE "List" ADD COLUMN ".ownerId" VARCHAR NOT NULL; + +ALTER TABLE "Task" ADD COLUMN ".ownerId" VARCHAR NOT NULL; + +ALTER TABLE "User" ADD COLUMN ".ownerId" VARCHAR NOT NULL; + ALTER TABLE "List" ADD PRIMARY KEY ("id"); ALTER TABLE "Task" ADD PRIMARY KEY ("id"); @@ -37,9 +43,3 @@ ALTER TABLE "Task" ADD CONSTRAINT fk_task_3 FOREIGN KEY ("list_id") REFERENCES " ALTER TABLE "User" ADD CONSTRAINT uq_user_4 UNIQUE (".ownerId"); -ALTER TABLE "List" ADD COLUMN ".ownerId" VARCHAR NOT NULL; - -ALTER TABLE "Task" ADD COLUMN ".ownerId" VARCHAR NOT NULL; - -ALTER TABLE "User" ADD COLUMN ".ownerId" VARCHAR NOT NULL; - diff --git a/bbconf/bb_todo-resolver.toml b/bbconf/bb_todo-resolver.toml index 45335b6..3d7d509 100644 --- a/bbconf/bb_todo-resolver.toml +++ b/bbconf/bb_todo-resolver.toml @@ -1,6 +1,31 @@ # -# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-11-01 03:18:33+01:00 +# Generated by bbc (basebox compiler) version 0.1.0-beta.23 at 2023-11-01 10:35:59+01:00 # +[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.updateTask] operation_name = "updateTask" @@ -36,45 +61,39 @@ column = "id" condition_str = "= '$id'" index = "" -[resolvers.getUser] -operation_name = "getUser" +[resolvers.createTask] +operation_name = "createTask" -[resolvers.getUser.resolver.QueryBuilder] -command_type = "SQLSelect" +[resolvers.createTask.resolver.QueryBuilder] +command_type = "SQLInsert" -[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.deleteTask] -operation_name = "deleteTask" - -[resolvers.deleteTask.resolver.QueryBuilder] -command_type = "SQLDelete" - -[resolvers.deleteTask.resolver.QueryBuilder.command] +[resolvers.createTask.resolver.QueryBuilder.command] table = "Task" command_type = "SQLSelect" columns = [] -modify_values = [] nested_modify_tables = [] +where_clauses = [] aggregate_result = true -[[resolvers.deleteTask.resolver.QueryBuilder.command.where_clauses]] -table = "Task" -column = "id" -condition_str = "= '$id'" -index = "" +[[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.createUser] operation_name = "createUser" @@ -98,6 +117,46 @@ value = "'$username'" column = "name" value = "'$name'" +[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.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.createList] operation_name = "createList" @@ -143,65 +202,6 @@ 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.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" diff --git a/src/App.vue b/src/App.vue index 2e77ce7..3dca8e3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,13 +3,14 @@ import { RouterLink, RouterView } from 'vue-router' import { store, loggedIn } from './store.js' import {clearSession} from "./store.js"; import {Modal} from "bootstrap"; +import { getOidcUserManager } from './util/oidc'; /** * Logout the user. */ function logOut() { clearSession(); - location.href = "/"; + getOidcUserManager().signoutRedirect(); } /** @@ -48,7 +49,7 @@ function dismissError() { diff --git a/src/components/Lists.vue b/src/components/Lists.vue index 4e75943..ff79a14 100644 --- a/src/components/Lists.vue +++ b/src/components/Lists.vue @@ -53,9 +53,9 @@ \ No newline at end of file diff --git a/src/components/auth/Login.vue b/src/components/auth/Login.vue deleted file mode 100644 index e69de29..0000000 diff --git a/src/router/index.js b/src/router/index.js index 1b5b3f1..9e632ca 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -5,7 +5,7 @@ * https://basebox.io */ -import {loggedIn, showError, store} from "../store"; +import {loggedIn, showError, storeInit } from "../store"; import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import { objectToQueryString } from "../util/net"; @@ -39,7 +39,7 @@ router.beforeEach(async (to, from) => { /* catch OIDC callback */ if (to.path === callbackPath) { - console.info("OIDC: got signin callback request to ", to); + console.info("OIDC: got OIDC callback request to ", to); const userMgr = getOidcUserManager(); try { const user = await userMgr.signinRedirectCallback(location.origin + to.fullPath); @@ -47,6 +47,7 @@ router.beforeEach(async (to, from) => { storeInit(user); } catch (err) { console.error("OIDC: Error handling signin callback: ", err); + showError("The login process failed: " + err); } /* redirect to home page */ return {name: 'home'}; diff --git a/src/store.js b/src/store.js index c51c011..368f85f 100644 --- a/src/store.js +++ b/src/store.js @@ -5,7 +5,7 @@ * https://basebox.io */ import { reactive } from 'vue' -import {gqlQuery} from "./util/net"; +import {gqlRequest} from "./util/net"; export const store = reactive({ @@ -60,6 +60,18 @@ export function clearError() { store.fatalError = ""; } +/** + * Remove a task from the store. + * + * This does not remove it from the database/server! + */ +export function removeTask(task) { + const idx = store.tasks.findIndex(item => item.id === task.id); + if (idx !== -1) { + store.tasks.splice(idx, 1); + } +} + /** * Initialize data store and database. * @@ -88,7 +100,7 @@ export async function storeInit(user) { */ try { console.info("NOTE: If next request fails, it is probably because the user already exists. In this case, the error is ignored."); - await gqlQuery(`mutation { + await gqlRequest(`mutation { createUser( username: "${store.userId}", name: "${store.userDisplayName}" @@ -108,7 +120,7 @@ export async function storeInit(user) { } /* Load user info, lists and todos. */ - gqlQuery(`query { + gqlRequest(`query { getUser(username: "${store.userId}") { name lists { @@ -131,14 +143,18 @@ export async function storeInit(user) { /* create default list if necessary */ if (store.lists.length === 0) { console.info("No lists found for current user; creating default list."); - gqlQuery(`mutation { + gqlRequest(`mutation { createList( title: "Default", user: { username: "${store.userId}" } ) { title + id } - }`); + }`).then(data => { + /* Save new default list in store */ + store.lists.push(data.createList); + }); } /* fix for basebox bug: task.list is returned as an array of List objects, where we expect just a single object */ diff --git a/src/util/net.js b/src/util/net.js index eb70997..6cb7f49 100644 --- a/src/util/net.js +++ b/src/util/net.js @@ -8,7 +8,7 @@ import {clearSession, store} from "../store"; /** - * GqlError - custom error thrown by the gqlQuery function. + * GqlError - custom error thrown by the gqlRequest function. * * Since the GraphQL server might through multiple errors at once, * this error class uses a message array. @@ -102,7 +102,7 @@ class GqlError extends Error { * Error with error message array on failure */ -export function gqlQuery(query) +export function gqlRequest(query) { /* prepare fetch options */ const fetchOpt = { @@ -113,9 +113,9 @@ export function gqlQuery(query) body: JSON.stringify({ query: query }), }; - /* if we're logged in, we add the authorization header */ - if (store.session) { - fetchOpt.headers["Authorization"] = `Bearer ${store.session.token}`; + /* if we're logged in, we add the authorization header with the access token */ + if (store.user) { + fetchOpt.headers["Authorization"] = `Bearer ${store.user.access_token}`; } console.info(`Sending request:\n${query}`); diff --git a/src/util/oidc.js b/src/util/oidc.js index 4966f45..49c604e 100644 --- a/src/util/oidc.js +++ b/src/util/oidc.js @@ -1,6 +1,9 @@ /** * Configure oidc-client-ts library and retrieve UserManager instance for authentication. * + * The documentation of oidc-client-ts is very...thin; this might be helpful: + * + * */ import { Log, UserManager, WebStorageStateStore } from 'oidc-client-ts'; @@ -11,13 +14,18 @@ Log.level = (process.env.NODE_ENV === 'production') ? Log.ERROR : Log.DEBUG; // OIDC configuration const oidcProviderDomain = 'https://basebox-test-1.eu.auth0.com'; const clientId = '5wl8hQV1thh07rScSoJ3aN56ETuXWprg'; +const clientSecret = 'QlHMvIffKLRviCcSu_bPQcf8e4dc6WeS3BwZE1r1F-9R30AFoeYEwaOazAuFenI5'; const scopes = "openid profile email name nickname"; export const callbackPath = "/auth/callback" /* OIDC UserManager singleton */ let userMgr = null; -// OIDC Client +/** + * Get OIDC UserManager singleton. + * + * @returns OIDC UserManager singleton. + */ export const getOidcUserManager = () => { if (userMgr) { return userMgr; @@ -27,11 +35,12 @@ export const getOidcUserManager = () => { userStore: new WebStorageStateStore(), authority: oidcProviderDomain, client_id: clientId, + client_secret: clientSecret, redirect_uri: window.location.origin + callbackPath, response_type: 'code', response_mode: 'query', scope: scopes, - post_logout_redirect_uri: window.location.origin + '/home?action=logout', + post_logout_redirect_uri: window.location.origin, accessTokenExpiringNotificationTime: 10, automaticSilentRenew: false, filterProtocolClaims: false,