WIP
This commit is contained in:
@ -1,3 +1,9 @@
|
||||
<!--
|
||||
Component that dispalys authentication related errors.
|
||||
|
||||
Part of the basebox sample Todo app.
|
||||
https://basebox.tech
|
||||
-->
|
||||
<template>
|
||||
|
||||
<div id="error-message">
|
||||
|
@ -1,6 +1,8 @@
|
||||
<!--
|
||||
Dummy component that handles OAuth login callback requests.
|
||||
|
||||
Part of the basebox sample Todo app.
|
||||
https://basebox.tech
|
||||
-->
|
||||
|
||||
<script setup>
|
||||
|
@ -1,3 +1,9 @@
|
||||
<!--
|
||||
About component.
|
||||
|
||||
Part of the basebox sample Todo app.
|
||||
https://basebox.tech
|
||||
-->
|
||||
<script setup>
|
||||
import AboutItem from './AboutItem.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>
|
||||
|
||||
import { store} from "../store";
|
||||
|
@ -55,7 +55,7 @@ router.beforeEach(async (to, from) => {
|
||||
});
|
||||
if (!response.ok) {
|
||||
/* 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 {
|
||||
name: 'oauth-error',
|
||||
query: {
|
||||
|
@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Network related functions.
|
||||
*
|
||||
* markus.thielen@basebox.health
|
||||
* Part of the basebox sample Todo app.
|
||||
* https://basebox.tech
|
||||
*/
|
||||
|
||||
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
|
||||
* @returns {string} query string, e.g. "parm1=778&read=all"
|
||||
*/
|
||||
@ -170,3 +172,4 @@ export function objectToQueryString(obj) {
|
||||
}
|
||||
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>
|
||||
import { store } from "../store";
|
||||
|
||||
import TodoRoot from "../components/TodoRoot.vue";
|
||||
|
||||
/**
|
||||
* Perform a login.
|
||||
@ -13,11 +20,15 @@ function login() {
|
||||
|
||||
<template>
|
||||
<main>
|
||||
|
||||
<!-- Force user to log in before he/she can see tasks. -->
|
||||
<div v-if="!store.session" id="login-prompt">
|
||||
<p>Your are currently not logged in.</p>
|
||||
<button class="btn btn-primary" @click="login" type="button">Login</button>
|
||||
</div>
|
||||
|
||||
<TodoRoot v-if="store.session" />
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
Reference in New Issue
Block a user