Authentication etc.
This commit is contained in:
parent
e587974a29
commit
d96e86ba56
@ -45,21 +45,16 @@ idp_url = "https://kcdev.basebox.health:8443"
|
||||
# OpenID Connect scope; default is "openid profile email"
|
||||
scope = "openid profile email"
|
||||
|
||||
# Optional base URL for OAuth2 URLs, e.g. "https://domain.tld/auth"
|
||||
# If omitted, it will be derived from the fields in the [server] section.
|
||||
# base_url = "http://127.0.0.1:8080"
|
||||
# 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"
|
||||
|
||||
# Will be appended to `base_url` to form the OAuth2 callback URL
|
||||
redirect_path = "/oauth/callback"
|
||||
|
||||
# Set to true to get a user's additional claims from OAuth2
|
||||
user_info_additional_claims_required = true
|
||||
|
||||
# On successful login (auth code flow complete), the browser can optionally
|
||||
# be redirected to the application URL.
|
||||
# If this is unset, the browser gets an empty 200 response on successful
|
||||
# authorization code flow completion.
|
||||
client_app_url = "http://127.0.0.1:5173/"
|
||||
# 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
|
||||
@ -71,6 +66,9 @@ login_path = "/oauth/login"
|
||||
# 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
|
||||
|
@ -22,7 +22,7 @@ function logOut() {
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
<a v-if="store.loggedIn" href="" @click="logOut">Log Out</a>
|
||||
<a v-if="store.session" href="" @click="logOut">Log Out</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
35
src/components/OAUthError.vue
Normal file
35
src/components/OAUthError.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
|
||||
<div id="error-message">
|
||||
<h3>An error occurred...</h3>
|
||||
|
||||
<p>{{ errorMsg }}</p>
|
||||
|
||||
<p>
|
||||
<a href="/" class="btn btn-primary">Start Over</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "OAuthError",
|
||||
props: ["errorMsg"],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#error-message {
|
||||
margin: 5rem 0;
|
||||
border: 1px solid #ff5050;
|
||||
border-radius: .5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
p {
|
||||
margin: 50px 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
25
src/components/OAuthCallback.vue
Normal file
25
src/components/OAuthCallback.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
Dummy component that handles OAuth login callback requests.
|
||||
|
||||
-->
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
/**
|
||||
* onMounted lifecycle hook
|
||||
*/
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
|
||||
export default {
|
||||
name: "OAuthCallback"
|
||||
|
||||
mounted: {
|
||||
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
</script>
|
@ -5,8 +5,11 @@
|
||||
* https://basebox.tech
|
||||
*/
|
||||
|
||||
import { store } from "../store";
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import OAUthError from "../components/OAUthError.vue";
|
||||
import { objectToQueryString } from "../util/net";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -23,8 +26,68 @@ const router = createRouter({
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AboutView.vue')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/oauth-error',
|
||||
name: 'oauth-error',
|
||||
component: OAUthError,
|
||||
props: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
|
||||
// Handle OAuth callback request.
|
||||
// This request is coming in after the user entered her/his credentials at the IdP
|
||||
// login form; the IdP (e.g. Keycloak) redirects the browser to the URL of this route.
|
||||
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 get session token: " + response.statusText);
|
||||
return {
|
||||
name: 'oauth-error',
|
||||
query: {
|
||||
code: response.status,
|
||||
addlInfo: await response.text()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* redirect to home */
|
||||
return { name: 'home' };
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default router
|
||||
|
11
src/store.js
11
src/store.js
@ -5,16 +5,17 @@
|
||||
* https://basebox.tech
|
||||
*/
|
||||
import { reactive } from 'vue'
|
||||
import { ref } from "vue";
|
||||
|
||||
export const store = reactive({
|
||||
|
||||
/** true if a user is currently logged in */
|
||||
loggedIn: ref(false),
|
||||
|
||||
/** Username of the currently logged-in user */
|
||||
userName: ref("stranger"),
|
||||
userName: "stranger",
|
||||
|
||||
/** The host that runs basebox and waits for GraphQL requests */
|
||||
baseboxHost: "http://127.0.0.1:8080",
|
||||
|
||||
/** basebox session data */
|
||||
session: null,
|
||||
|
||||
})
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
* markus.thielen@basebox.health
|
||||
*/
|
||||
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
import {store} from "../store";
|
||||
import router from "../router";
|
||||
|
||||
/**
|
||||
* GqlError - custom error thrown by the gqlQuery function.
|
||||
@ -107,8 +107,8 @@ export function gqlQuery(query)
|
||||
};
|
||||
|
||||
/* if we're logged in, we add the authorization header */
|
||||
if (store.getters.isLoggedIn) {
|
||||
fetchOpt.headers["Authorization"] = store.getters.authData.token;
|
||||
if (store.session) {
|
||||
fetchOpt.headers["Authorization"] = `Bearer ${store.session.token}`;
|
||||
}
|
||||
|
||||
console.info(fetchOpt);
|
||||
@ -157,3 +157,16 @@ export function gqlQuery(query)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode object as a query string
|
||||
* @param obj - a JavaScript object to encode
|
||||
* @returns {string} query string, e.g. "parm1=778&read=all"
|
||||
*/
|
||||
export function objectToQueryString(obj) {
|
||||
const str = [];
|
||||
for (let p in obj)
|
||||
if (obj.hasOwnProperty(p)) {
|
||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
||||
}
|
||||
return str.join("&");
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ function login() {
|
||||
<template>
|
||||
<main>
|
||||
<!-- Force user to log in before he/she can see tasks. -->
|
||||
<div v-if="!store.loggedIn" id="login-prompt">
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user