login, logout, store, ...

This commit is contained in:
Markus Thielen 2023-03-09 11:19:53 +01:00
parent 896ca59ace
commit b5a169282b
7 changed files with 70 additions and 115 deletions

View File

@ -2,12 +2,13 @@
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/Welcome.vue'
import { store } from './store.js'
import {clearSession} from "./store.js";
/**
* Logout the user.
*/
function logOut() {
store.loggedIn = false;
clearSession();
location.href = "/";
}
</script>
@ -22,7 +23,7 @@ function logOut() {
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<a v-if="store.session" href="" @click="logOut">Log Out</a>
<a v-if="store.session" href="" @click="logOut()">Log Out</a>
</nav>
</div>
</header>

View File

@ -1,27 +0,0 @@
<!--
Dummy component that handles OAuth login callback requests.
Part of the basebox sample Todo app.
https://basebox.tech
-->
<script setup>
import { onMounted } from 'vue'
/**
* onMounted lifecycle hook
*/
onMounted(() => {
});
export default {
name: "OAuthCallback"
mounted: {
},
}
</script>

View File

@ -10,6 +10,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import OAUthError from "../components/OAUthError.vue";
import { objectToQueryString } from "../util/net";
import {createUser, oauthCallbackHandler} from "../util/oauth";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -44,48 +45,23 @@ router.beforeEach(async (to, from) => {
if (to.path === "/oauth-callback") {
let queryString = objectToQueryString(to.query);
console.info(`Got /oauth-callback with query string '${queryString}`);
/* Pass the query parameters to the broker to complete login. We will get
* the session token and user data in return.
*
* The URL we post to is broker's base URL and the `openid_connect_path`
* setting from broker's config.
*/
const response = await fetch(`${store.baseboxHost}/oauth/complete-login?${queryString}`, {
method: "POST",
});
if (!response.ok) {
/* redirect to error component */
console.error("Failed to complete login/get session data: " + response.statusText);
return {
name: 'oauth-error',
query: {
code: response.status,
addlInfo: await response.text()
}
}
/* Pass the query string to the handler function */
try {
await oauthCallbackHandler(queryString);
/* Success; redirect to home */
} catch (err) {
console.error("Failed to finish login: " + err);
}
/* store session data; this is a JSON object of the following form:
{
"token":"47caa9df-ac89-45a8-8b22-9c0925d6aed0",
"username":"tester",
"first_name":"Fred",
"last_name":"Feuerstein",
"roles":[
"/user"
],
"claims":{
"groups":[
"/user"
]
}
}
*/
store.session = await response.json();
store.userName = store.session.first_name ? store.session.first_name : store.session.username;
/* We have to create the logged-in user explicitly; see comment for `createUser()` */
try {
await createUser();
} catch (err) {
console.error("Failed to create user: " + err);
}
/* redirect to home */
return { name: 'home' };
return {name: 'home'};
}
});

View File

@ -11,11 +11,27 @@ export const store = reactive({
/** Username of the currently logged-in user */
userName: "stranger",
/** The host that runs basebox and waits for GraphQL requests */
/** base URL of basebox broker host */
baseboxHost: "http://127.0.0.1:8080",
/** basebox session data */
session: null,
session: {},
});
/**
* Return true if the user is logged in.
*/
export function loggedIn() {
return store.session && store.session.token;
}
/**
* Clear session data.
*/
export function clearSession() {
store.session = {};
}
})

View File

@ -5,8 +5,7 @@
* https://basebox.tech
*/
import {store} from "../store";
import router from "../router";
import {clearSession, store} from "../store";
/**
* GqlError - custom error thrown by the gqlQuery function.
@ -114,7 +113,7 @@ export function gqlQuery(query)
console.info(fetchOpt);
return fetch(store.getters.gqlUrl, fetchOpt).then(
return fetch(`${store.baseboxHost}/graphql`, fetchOpt).then(
/* fetch success */
async response => {
if (response.ok) {
@ -128,27 +127,25 @@ export function gqlQuery(query)
} else {
/* some errors... */
return new Promise((resolve, reject) => {
const error = new GqlError(rspJson);
/* if this is a 401, we redirect to the login page */
if (error.is401()) {
router.push({
name: "Login",
query: {
next: router.currentRoute.fullPath,
}
});
}
reject(new GqlError(rspJson));
});
}
} else {
/* low level/network error */
/* if this is a 401, we clear the session data */
if (response.status === 401) {
console.error("Last request failed: unauthorized. Clearing session.");
clearSession();
return new Promise((resolve, reject) => reject("Unauthorized"));
}
return new Promise((resolve, reject) => {
reject(new GqlError(response.statusText));
});
}
},
/* fetch failure */
reason => {
return new Promise((resolve, reject) => {

View File

@ -44,23 +44,18 @@ import {gqlQuery} from "./net";
*/
export async function oauthCallbackHandler(queryString) {
fetch(`${store.baseboxHost}/oauth/complete-login?${queryString}`, {
/* Pass query string to the broker to complete the login process. */
const response = await 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());
});
if (!response.ok) {
throw new Error("Failed to get session data: " + response.statusText);
}
/* Store session data */
const rspJson = await response.json();
store.session = { ...rspJson };
store.userName = store.session.first_name ? store.session.first_name : store.session.username;
}
/**
@ -70,21 +65,17 @@ export async function oauthCallbackHandler(queryString) {
* 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>}
* @throws Error if the user creation failed.
*/
async function createUser(username, firstName, lastName) {
export async function createUser() {
gqlQuery(`query {
await gqlQuery(`mutation {
createUser(
username: "${username}",
name: "${firstName} ${lastName}"
)`
).catch(err => {
});
username: "${store.session.username}",
name: "${store.session.first_name} ${store.session.last_name}"
) {
username
}
}`);
}

View File

@ -7,6 +7,7 @@
<script setup>
import { store } from "../store";
import {loggedIn} from "../store";
import TodoRoot from "../components/TodoRoot.vue";
/**
@ -22,12 +23,12 @@ function login() {
<main>
<!-- Force user to log in before he/she can see tasks. -->
<div v-if="!store.session" id="login-prompt">
<div v-if="!loggedIn()" 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" />
<TodoRoot v-if="loggedIn()" />
</main>
</template>