✨ Add oidc
This commit is contained in:
parent
f4b04691f5
commit
424b9ed713
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG VERSION
|
||||
|
||||
RUN npm install
|
||||
|
||||
RUN npx nuxi cleanup
|
||||
|
||||
RUN npx nuxi build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
18
Jenkinsfile
vendored
18
Jenkinsfile
vendored
@ -2,26 +2,10 @@ pipeline {
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Tag git commit') {
|
||||
steps {
|
||||
script {
|
||||
sshagent(['jenkins']) {
|
||||
sh """
|
||||
git config user.email "jenkins"
|
||||
git config user.name "jenkins@dinauer-paf.de"
|
||||
|
||||
git tag -a build-${env.BUILD_NUMBER} -m "Jenkins Build ${env.BUILD_NUMBER}"
|
||||
|
||||
git push origin build-${env.BUILD_NUMBER}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Set Image Name') {
|
||||
steps {
|
||||
script {
|
||||
env.IMAGE = "harbor.dinauer.dev/kubooboo/frontend:${env.BUILD_NUMBER}";
|
||||
env.IMAGE = "harbor.dinauer.dev/registry/frontend:${env.BUILD_NUMBER}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
app/app.vue
37
app/app.vue
@ -1,6 +1,15 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<div>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath === '/' }" to="/"><UiIcon>home</UiIcon>Home</NuxtLink>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath.startsWith('/artifacts') }" to="/artifacts"><UiIcon>box</UiIcon>Artifacts</NuxtLink>
|
||||
</div>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath.startsWith('/settings') }" to="/settings"><UiIcon>settings</UiIcon>Settings</NuxtLink>
|
||||
</div>
|
||||
<NuxtPage></NuxtPage>
|
||||
</div>
|
||||
<PopupTemplate v-if="popup" :heading="popup.config.heading" :size="popup.config.size">
|
||||
<component :is="popup.component"></component>
|
||||
</PopupTemplate>
|
||||
@ -10,8 +19,13 @@
|
||||
<script setup lang="ts">
|
||||
import {usePopup} from "~/components/ui/popup/Popup";
|
||||
import PopupTemplate from "~/components/ui/popup/PopupTemplate.vue";
|
||||
import {useAccountStore} from "~/auth/useAccountStore";
|
||||
|
||||
const popup = computed(() => usePopup().get());
|
||||
|
||||
onMounted(() => {
|
||||
useAccountStore().init();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -19,4 +33,27 @@ const popup = computed(() => usePopup().get());
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
.page {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
.page > * {
|
||||
padding: 1rem;
|
||||
}
|
||||
.sidebar {
|
||||
background-color: var(--tile-color);
|
||||
border-right: 1px solid #cddaff;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
.link {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.link.active, .link.active * {
|
||||
background-color: #3c74ff;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,10 @@
|
||||
import axios from "axios";
|
||||
import {AuthApi} from "~/utils/AuthApi";
|
||||
import {Api} from "~/utils/Api";
|
||||
|
||||
export class Account
|
||||
{
|
||||
static baseURL: "http://localhost:8089/api/iam-backend/realms/key/accounts";
|
||||
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
username?: string;
|
||||
@ -11,22 +13,16 @@ export class Account
|
||||
roles?: string[];
|
||||
initial?: boolean;
|
||||
|
||||
static get(id: string, token: string, onSuccess: (account: Account) => void)
|
||||
static get(onSuccess: (account: Account) => void)
|
||||
{
|
||||
AuthApi.get(StringUtils.format('/accounts/%s', id), {
|
||||
headers: {
|
||||
Authorization: StringUtils.format("Bearer %s", token)
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
onSuccess(response.data);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
static create(user: AccountCreation, onSuccess: () => void)
|
||||
{
|
||||
AuthApi.post('/accounts', user)
|
||||
.then(() => {
|
||||
AuthApi.create().post('/accounts', user)
|
||||
.then(() =>
|
||||
{
|
||||
onSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,23 +1,28 @@
|
||||
import {type AxiosResponse} from "axios";
|
||||
import {AuthApi} from "~/utils/AuthApi";
|
||||
import {Api} from "~/utils/Api";
|
||||
import type {Account} from "~/auth/Account";
|
||||
|
||||
export class Session
|
||||
{
|
||||
static baseURL = "http://localhost:8089";
|
||||
static COOKIE = "session";
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
public user: Account,
|
||||
public token: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
static create(sessionCreation: SessionCreation, onSuccess: (token: string) => void, onError: () => void)
|
||||
{
|
||||
AuthApi.post<string>("/sessions", sessionCreation)
|
||||
.then((response: AxiosResponse) => {
|
||||
AuthApi.create().post<string>("/sessions/" + useRuntimeConfig().public.clientId, sessionCreation)
|
||||
.then((response: AxiosResponse) =>
|
||||
{
|
||||
onSuccess(response.data);
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(() =>
|
||||
{
|
||||
onError();
|
||||
});
|
||||
}
|
||||
@ -25,8 +30,10 @@ export class Session
|
||||
|
||||
export class SessionCreation
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public email?: string,
|
||||
public password?: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,20 @@
|
||||
import {MavenApi} from "~/utils/MavenApi";
|
||||
|
||||
export class Token
|
||||
{
|
||||
static baseURL: "http://localhost:8080";
|
||||
|
||||
constructor(
|
||||
public name: string,
|
||||
public createdAt: Date,
|
||||
public expiresAt: Date
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
static get(onSuccess: (tokens: Token[]) => void)
|
||||
{
|
||||
MavenApi.get<Token[]>("/tokens")
|
||||
.then((response) => {
|
||||
MavenApi.create().get<Token[]>("/tokens")
|
||||
.then((response) =>
|
||||
{
|
||||
onSuccess(response.data);
|
||||
});
|
||||
}
|
||||
@ -22,12 +25,15 @@ export class TokenCreation
|
||||
constructor(
|
||||
public name: string,
|
||||
public expiresAt: Date
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
static create(tokenCreation: TokenCreation, onSuccess: (token: TokenSecret) => void)
|
||||
{
|
||||
MavenApi.post<TokenSecret>("/tokens", tokenCreation)
|
||||
.then((response) => {
|
||||
MavenApi.create().post<TokenSecret>("/tokens", tokenCreation)
|
||||
.then((response) =>
|
||||
{
|
||||
onSuccess(response.data);
|
||||
});
|
||||
}
|
||||
@ -40,5 +46,7 @@ export class TokenSecret
|
||||
public expiresAt: Date,
|
||||
public createdAt: Date,
|
||||
public token: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
25
app/auth/useAccountStore.ts
Normal file
25
app/auth/useAccountStore.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {Account} from "~/auth/Account";
|
||||
|
||||
export const useAccountStore = defineStore('account', {
|
||||
state: () => ({
|
||||
account: undefined as Account | undefined
|
||||
}),
|
||||
getters: {
|
||||
require: (state) =>
|
||||
{
|
||||
return (): Account | undefined =>
|
||||
{
|
||||
return state.account;
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
init()
|
||||
{
|
||||
Account.get((account: Account) =>
|
||||
{
|
||||
this.account = account;
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,29 +1,34 @@
|
||||
import axios from "axios";
|
||||
import {Api, MavenApi} from "~/utils/Api";
|
||||
|
||||
export class Artifact
|
||||
{
|
||||
public baseURL = "http://localhost:8080";
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
public id: string,
|
||||
public groupId: string,
|
||||
public artifactId: string,
|
||||
public updatedAt: string,
|
||||
public versions: Version[],
|
||||
public totalPullCount: number
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
static get(onSuccess: (artifacts: Artifact[]) => void)
|
||||
{
|
||||
axios.get<Artifact[]>("http://localhost:8080/artifacts")
|
||||
.then((response) => {
|
||||
MavenApi.create().get<Artifact[]>("/artifacts")
|
||||
.then((response) =>
|
||||
{
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
|
||||
static getById(id: string, onSuccess: (artifact: Artifact) => void)
|
||||
{
|
||||
axios.get<Artifact>("http://localhost:8080/artifacts/" + id)
|
||||
.then((response) => {
|
||||
MavenApi.create().get<Artifact>("/artifacts/" + id)
|
||||
.then((response) =>
|
||||
{
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
@ -31,28 +36,34 @@ export class Artifact
|
||||
|
||||
export class Version
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public id: string,
|
||||
public groupId: string,
|
||||
public artifactId: string,
|
||||
public version: string,
|
||||
public jars?: Jar[],
|
||||
public pom?: Pom
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
export class Jar
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public filename: string,
|
||||
public url: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
export class Pom
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public filename: string,
|
||||
public url: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<ContentRow class="artifact pointer" @click="() => useRouter().push('artifacts/' + artifact.id)">
|
||||
<ContentCell>{{ artifact.groupId }}</ContentCell>
|
||||
<ContentCell>{{ artifact.artifactId }}</ContentCell>
|
||||
<ContentCell v-for="latest in [artifact.versions.at(0)]"><span v-if="latest">{{ latest.version }}</span></ContentCell>
|
||||
<ContentCell><div v-if="artifact.versions" v-for="latest in [artifact.versions.at(0)]"><span v-if="latest">{{ latest.version }}</span></div></ContentCell>
|
||||
<ContentCell>{{ artifact.totalPullCount }}</ContentCell>
|
||||
<ContentCell>{{ dayjs(artifact.updatedAt).format("DD.MM.YYYY HH:mm") + " Uhr" }}</ContentCell>
|
||||
</ContentRow>
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
import axios from "axios";
|
||||
import type {Account} from "~/auth/Account";
|
||||
import {Session} from "~/auth/Session";
|
||||
|
||||
export class Event
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public type: EventType,
|
||||
public timestamp: Date,
|
||||
public resource: Resource,
|
||||
public account: Account
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
static get(onSuccess: (events: Event[]) => void)
|
||||
{
|
||||
axios.get<Event[]>("http://localhost:8080/events")
|
||||
.then((response) => {
|
||||
axios.get<Event[]>("http://localhost:8080/events", {withCredentials: true})
|
||||
.then((response) =>
|
||||
{
|
||||
onSuccess(response.data)
|
||||
});
|
||||
}
|
||||
@ -21,11 +25,13 @@ export class Event
|
||||
|
||||
export class Resource
|
||||
{
|
||||
constructor (
|
||||
constructor(
|
||||
public groupId: string,
|
||||
public artifactId: string,
|
||||
public version: string
|
||||
) {}
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
export enum EventType
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import {Session} from "~/auth/Session";
|
||||
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
const authenticated = useCookie<Session>(Session.COOKIE).value != null;
|
||||
const path = to.path;
|
||||
if (StringUtils.startsWith(path, '/app') && !authenticated)
|
||||
{
|
||||
return navigateTo('/')
|
||||
}
|
||||
if (StringUtils.equals(path, '/') && authenticated)
|
||||
{
|
||||
return navigateTo('/app')
|
||||
}
|
||||
})
|
||||
@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<div>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath === '/app' }" to="/app"><UiIcon>home</UiIcon>Home</NuxtLink>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath.startsWith('/app/artifacts') }" to="/app/artifacts"><UiIcon>box</UiIcon>Artifacts</NuxtLink>
|
||||
</div>
|
||||
<NuxtLink class="link left-center" :class="{ active: useRoute().fullPath.startsWith('/app/settings') }" to="/app/settings"><UiIcon>settings</UiIcon>Settings</NuxtLink>
|
||||
</div>
|
||||
<NuxtPage></NuxtPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
.page > * {
|
||||
padding: 1rem;
|
||||
}
|
||||
.sidebar {
|
||||
background-color: var(--tile-color);
|
||||
border-right: 1px solid #cddaff;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
.link {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.link.active, .link.active * {
|
||||
background-color: #3c74ff;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div class="content-l">
|
||||
<h1>Home</h1>
|
||||
<h2>Recent Events</h2>
|
||||
<EventList></EventList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import EventList from "~/components/events/EventList.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="content-m">
|
||||
<h2>Files</h2>
|
||||
<p class="tile-m" v-if="selectedVersion.jars" v-for="jar in selectedVersion.jars" @click="Download.download(jar.url)">{{ jar.filename }}</p>
|
||||
<a class="tile-m" v-if="selectedVersion.pom" :href="MavenApi.defaults.baseURL + '/maven2/' + selectedVersion.pom.url" target="_blank">{{ selectedVersion.pom.filename }}</a>
|
||||
<a class="tile-m" v-if="selectedVersion.pom" :href="Api.defaults.baseURL + '/maven2/' + selectedVersion.pom.url" target="_blank">{{ selectedVersion.pom.filename }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-m">
|
||||
@ -29,7 +29,6 @@
|
||||
import {Artifact, Version} from "~/components/artifact/Artifact";
|
||||
import Codebox from "~/components/artifact/Codebox.vue";
|
||||
import {Download} from "~/utils/Download";
|
||||
import {MavenApi} from "~/utils/MavenApi";
|
||||
const artifact: Ref<Artifact | undefined> = ref(undefined);
|
||||
const selectedVersion: Ref<Version | undefined> = ref(undefined);
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<LoginComponent></LoginComponent>
|
||||
<div class="content-l">
|
||||
<h1>Home</h1>
|
||||
<h2>Recent Events</h2>
|
||||
<EventList></EventList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import LoginComponent from "~/components/auth/LoginComponent.vue";
|
||||
import EventList from "~/components/events/EventList.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="col-3">
|
||||
<div class="tile-m">
|
||||
<h3>Username</h3>
|
||||
<DisplayNameComponent :account="session.user"></DisplayNameComponent>
|
||||
|
||||
</div>
|
||||
<div class="tile-m">
|
||||
<h3>E-Mail</h3>
|
||||
50
app/utils/Api.ts
Normal file
50
app/utils/Api.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import axios, {type AxiosInstance} from "axios";
|
||||
|
||||
export abstract class Api
|
||||
{
|
||||
public baseURL: string;
|
||||
|
||||
protected constructor(baseURL: string)
|
||||
{
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
build()
|
||||
{
|
||||
const instance: AxiosInstance = axios.create({
|
||||
baseURL: this.baseURL
|
||||
});
|
||||
instance.interceptors.request.use((config) =>
|
||||
{
|
||||
config.withCredentials = true;
|
||||
return config;
|
||||
})
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthApi extends Api
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super("http://localhost:8089/api/iam-backend");
|
||||
}
|
||||
|
||||
static create()
|
||||
{
|
||||
return new AuthApi().build();
|
||||
}
|
||||
}
|
||||
|
||||
export class MavenApi extends Api
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super("http://localhost:8080");
|
||||
}
|
||||
|
||||
static create()
|
||||
{
|
||||
return new MavenApi().build();
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import axios, {type AxiosInstance} from "axios";
|
||||
import {Session} from "~/auth/Session";
|
||||
|
||||
export const AuthApi: AxiosInstance = function()
|
||||
{
|
||||
const instance: AxiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:8089/api/iam-backend'
|
||||
});
|
||||
instance.interceptors.request.use((config) =>
|
||||
{
|
||||
const session: Session = useCookie<Session>(Session.COOKIE).value
|
||||
if (session)
|
||||
{
|
||||
config.headers["Authorization"] = StringUtils.format("Bearer %s", session.token)
|
||||
}
|
||||
return config;
|
||||
})
|
||||
return instance;
|
||||
}();
|
||||
@ -1,19 +0,0 @@
|
||||
import axios, {type AxiosInstance} from "axios";
|
||||
import {Session} from "~/auth/Session";
|
||||
|
||||
export const MavenApi: AxiosInstance = function()
|
||||
{
|
||||
const instance: AxiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:8080'
|
||||
});
|
||||
instance.interceptors.request.use((config) =>
|
||||
{
|
||||
const session: Session = useCookie<Session>(Session.COOKIE).value
|
||||
if (session)
|
||||
{
|
||||
config.headers["Authorization"] = StringUtils.format("Bearer %s", session.token)
|
||||
}
|
||||
return config;
|
||||
})
|
||||
return instance;
|
||||
}();
|
||||
@ -1,20 +1,27 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
devtools: {enabled: true},
|
||||
|
||||
css: [
|
||||
'@/assets/style.css',
|
||||
'@/assets/base-style.css'
|
||||
],
|
||||
|
||||
app: {
|
||||
head: {
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200' }
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
clientId: process.env.NUXT_PUBLIC_CLIENT_ID,
|
||||
idpUrl: process.env.NUXT_PUBLIC_IDP_URL
|
||||
}
|
||||
},
|
||||
modules: ['@pinia/nuxt']
|
||||
})
|
||||
11
server/middleware/auth.ts
Normal file
11
server/middleware/auth.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {Session} from "~/auth/Session";
|
||||
import {useRuntimeConfig} from "#imports";
|
||||
|
||||
export default defineEventHandler(async (event) =>
|
||||
{
|
||||
const cookie = getCookie(event, Session.COOKIE);
|
||||
if (cookie == null)
|
||||
{
|
||||
await sendRedirect(event, useRuntimeConfig().public.idpUrl, 302);
|
||||
}
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user