diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml deleted file mode 100644 index 53c75fe..0000000 --- a/.forgejo/workflows/build.yaml +++ /dev/null @@ -1,109 +0,0 @@ -on: - push: - workflow_dispatch: - inputs: - build: - description: "Build package and container" - required: true - default: false - type: boolean - -jobs: - build: - runs-on: container - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get the version from git - id: version - run: echo "version=$(git describe --dirty --tags)" >> $GITHUB_OUTPUT - - - name: Check if the package should be built - id: builder - env: - RUN: ${{ toJSON(inputs.build || !contains(steps.version.outputs.version, '-')) }} - run: | - echo "run=$RUN" >> $GITHUB_OUTPUT - echo "Run build: $RUN" - - - name: Info - version and if the image container should be built - env: - VERSION: ${{ steps.version.outputs.version }} - RUN: ${{ steps.builder.outputs.run }} - FORCE: ${{ toJSON(inputs.build) }} - run: | - echo "Version $VERSION, force (manual input): $FORCE, run the build: $RUN" - - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false - version: 10 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install - - - name: Update version.json from git describe - run: pnpm run version - - - name: Set the version in package.json - env: - VERSION: ${{ steps.version.outputs.version }} - run: sed "s/0.0.0/${VERSION}/" -i package.json - - - name: Build package (transpile ts => js) - run: pnpm run build --base /oidc-test-web - - - name: Set registry token for pnpm" - env: - LOCAL_NPM_TOKEN: ${{ secrets.LOCAL_NPM_TOKEN }} - run: pnpm set "//code.philo.ydns.eu/api/packages/philorg/npm/:_authToken=${LOCAL_NPM_TOKEN}" - - - name: Publish - if: fromJSON(steps.builder.outputs.run) - run: pnpm publish --no-git-checks - continue-on-error: true - - - name: Build container - if: fromJSON(steps.builder.outputs.run) - uses: actions/buildah-build@v1 - with: - image: oidc-vue-test - oci: true - labels: oidc-vue-test - tags: latest ${{ steps.version.outputs.version }} - containerfiles: | - ./Containerfile - build-args: | - APP_VERSION=${{ steps.version.outputs.version }} - - - name: Workaround for bug of podman-login - if: fromJSON(steps.builder.outputs.run) - run: | - mkdir -p $HOME/.docker - echo "{ \"auths\": {} }" > $HOME/.docker/config.json - - - name: Log in to container registry (with another workaround) - if: fromJSON(steps.builder.outputs.run) - uses: actions/podman-login@v1 - with: - registry: ${{ vars.REGISTRY }} - username: ${{ secrets.REGISTRY_USER }} - password: ${{ secrets.REGISTRY_PASSWORD }} - auth_file_path: /tmp/auth.json - - - name: Push the image to the registry - if: fromJSON(steps.builder.outputs.run) - uses: actions/push-to-registry@v2 - with: - registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}" - image: oidc-vue-test - tags: latest ${{ steps.version.outputs.version }} diff --git a/.gitignore b/.gitignore index eff8b6d..3659f1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ node_modules/* dist/* -settings.json -.env diff --git a/Containerfile b/Containerfile index f5612bd..e9815a2 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,3 @@ FROM docker.io/nginx:alpine -COPY ./dist /usr/share/nginx/html/oidc-test-web - -CMD ["nginx", "-g", "daemon off;"] +COPY ./dist /usr/share/nginx/html diff --git a/README.md b/README.md index 271f522..3f71700 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,20 @@ -# Test app for OIDC, OAuth2 - web app version written with Vue3 +# Test app for OIDC, OAuth2 - pure web client version written with Vue3 Small web app for experimenting a web app with a Keycloak auth server. -It is a sibling of the server version [oidc-test](philorg/oidc-fastapi-test), -which acts also as a resource server. +## Deployment -Live demo: : - -- configured with a test realm on a private Keycloak instance -- 2 users are defined: foo (foofoo) and bar (barbar). - -**Note**: decoding tokens requires the use of cryto extension, -that web browsers allow only with a secured connection (https). - -## Configuration - -The app expects that a `settings.json` file is available on the server -at the app's base url. - -For example: - -```json -{ - "keycloakUri": "https://keycloak.your.domain", - "realm": "test", - "authProvider": "keycloak", - "sso": false, - "clientId": "oidc-test-web", - "tokenSandbox": true, - "resourceServerUrl": "https://someserver.your.domain/resourceBaseUrl", - "resourceScopes": [ - "get:time", - "get:bs" - ], - "resourceProviders": { - "resourceProvider1": { - "name": "Third party 1", - "baseUrl": "https://otherserver.your.domain/resources/", - "verifySSL": true, - "resources": { - "public": { - "name": "A public resource", - "url": "resource/public" - }, - "bs": { - "name": "A secured resource, eg by scope", - "url": "resource/secured1" - }, - "time": { - "name": "Another secured resource, eg by role", - "url": "resource/secured2" - } - } - } - } -} -``` - -## Build - -For generating a `dist` directory ready to be copied to a web server -static data tree, it's a straightforward: - -```sh -pnpm run build -``` - -Eventually specify a `base url` (eg. accessible from `https://for.example.com/oidc-test-web`): +In a container: ```sh pnpm run build --base oidc-test-web -``` - -## Deployment - -Examples of deployment are presented below. - -- Using the nginx default container, from the development source tree: - -```sh -podman run -it --rm -p 8874:80 -v ./dist:/usr/share/nginx/html/oidc-test-web -v ./settings.json:/usr/share/nginx/html/oidc-test-web/settings.json docker.io/nginx:alpine -``` - -- The build is packaged in a provided container (see *pakcages*), serving with the `/oidc-test-web` base url: - -```sh -podman run -it --rm -p 8874:80 -v ./settings.json:/usr/share/nginx/html/oidc-test-web/settings.json code.philo.ydns.eu/philorg/oidc-vue-test:latest -``` - -- A *quadlet* *systemd* service, in `~/.config/containers/systemd/oidc-vue-test.container`: - -```systemd -[Container] -ContainerName=oidc-vue-test -Image=code.philo.ydns.eu/philorg/oidc-vue-test:latest -Mount=type=bind,source=/path/to/settings.json,destination=/usr/share/nginx/html/oidc-test-web/settings.json -PublishPort=8874:80 - -[Service] -Restart=always -RestartSec=5 - -[Unit] -After=podman-user-wait-network-online.service - -[Install] -WantedBy=default.target -``` - -Run with: - -```sh -systemctl --user daemon-reload -systemcrl --user start oidc-vue-test +podman run -it --rm -p 8874:80 -v ./dist:/usr/share/nginx/html/oidc-test-web docker.io/nginx:alpine ``` ## Frontend -YMMV, easy with *Caddy*: - ```Caddyfile -handle /oidc-test-web { +handle /iodc-test-web { reverse-proxy hostname.domainame:8874 } redir /oidc-test-web /oidc-test-web/ diff --git a/example_settings.json b/example_settings.json deleted file mode 100644 index 3c1f9f5..0000000 --- a/example_settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "keycloakUri": "https://philo.ydns.eu/auth", - "realm": "test", - "authProvider": "keycloak", - "sso": false, - "clientId": "oidc-test-web", - "resourceServerUrl": "https://philo.ydns.eu/oidc-test/resource", - "resourceScopes": [ - "get:time", - "get:bs" - ], - "tokenSandbox": true -} diff --git a/index.html b/index.html index fc972fe..e2443c1 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,10 @@ - OIDC test web - + Axios interceptor example
- + diff --git a/package.json b/package.json index 360dd30..7aae403 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,18 @@ { - "name": "oidc-test-web", + "name": "typescript", "version": "0.0.0", + "private": true, "type": "module", "scripts": { "dev": "vite --port 3000", "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", - "type-check": "vue-tsc --build", - "version": "echo \"{\\\"version\\\":\\\"$(git describe --tags --dirty --always)\\\"}\" > src/version.json" + "type-check": "vue-tsc --build" }, "dependencies": { - "@dsb-norge/vue-keycloak-js": "^3.0.1", + "@dsb-norge/vue-keycloak-js": "3.0.1", "axios": "1.7.9", - "keycloak-js": "^26.1.0", "vue": "3.5.13" }, "devDependencies": { @@ -26,8 +25,5 @@ "vite": "6.0.7", "vite-plugin-vue-devtools": "7.7.0", "vue-tsc": "2.2.0" - }, - "publishConfig": { - "registry": "http://code.philo.ydns.eu/api/packages/philorg/npm/" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 356bbae..8e9f286 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,11 @@ importers: .: dependencies: '@dsb-norge/vue-keycloak-js': - specifier: ^3.0.1 - version: 3.0.1(vue@3.5.13(typescript@5.6.3)) + specifier: 3.0.0 + version: 3.0.0(vue@3.5.13(typescript@5.6.3)) axios: specifier: 1.7.9 version: 1.7.9 - keycloak-js: - specifier: ^26.1.0 - version: 26.1.0 vue: specifier: 3.5.13 version: 3.5.13(typescript@5.6.3) @@ -194,8 +191,8 @@ packages: resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} engines: {node: '>=6.9.0'} - '@dsb-norge/vue-keycloak-js@3.0.1': - resolution: {integrity: sha512-uJ4deVw4Vyp2FrG0JjYAy8NE6zIlIIl/92mQDlSGH+9kc758hBdrCdZSD3aVzv/Lwh07tpOXsx4jXzbVQkPfkA==} + '@dsb-norge/vue-keycloak-js@3.0.0': + resolution: {integrity: sha512-PcANEYbCRWtvMLg9wPpGuBDR5wrvfC4nkOawGsF79zm6RQ6IfxGIpxWHoZSXEcRaYdPchkT4gCZ+dyp8wb7ccA==} peerDependencies: vue: '>=3.0.0' @@ -806,9 +803,6 @@ packages: keycloak-js@26.0.7: resolution: {integrity: sha512-vKCk1ISMiouYRLjMzi5fKp58RnYxKEBS29BoDgVfYwVW94IXchj9jLqBvIet31VD1v79l3WaWT+WMX7fH8O4wA==} - keycloak-js@26.1.0: - resolution: {integrity: sha512-3CTelLNADK6sIxGHCQmKlT3ezcIp8O3Iimmg+ybS78RHy+HAUkkoBaW/YuHGdYkfEDMBlrqD3u+CQ4vLsrmyFA==} - kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -1304,7 +1298,7 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@dsb-norge/vue-keycloak-js@3.0.1(vue@3.5.13(typescript@5.6.3))': + '@dsb-norge/vue-keycloak-js@3.0.0(vue@3.5.13(typescript@5.6.3))': dependencies: keycloak-js: 26.0.7 vue: 3.5.13(typescript@5.6.3) @@ -1831,8 +1825,6 @@ snapshots: keycloak-js@26.0.7: {} - keycloak-js@26.1.0: {} - kolorist@1.8.0: {} lru-cache@5.1.1: diff --git a/public/styles.css b/public/styles.css deleted file mode 100644 index 8d4804f..0000000 --- a/public/styles.css +++ /dev/null @@ -1,229 +0,0 @@ -body { - font-family: Arial, Helvetica, sans-serif; - background-color: floralwhite; - margin: 0; - font-family: system-ui; - text-align: center; -} -h1 { - background-color: #f786867d; - margin: 0 0 0.2em 0; - box-shadow: 0px 0.2em 0.2em #f786867d; - text-shadow: 0 0 2px #00000080; - font-weight: 200; -} -p { - margin: 0.2em; -} -hr { - margin: 0.2em; -} -.hidden { - display: none; -} -.center { - text-align: center; -} -.error { - color: darkred; -} -.content { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} -.user-info { - padding: 0.5em; - display: flex; - gap: 0.5em; - flex-direction: column; - width: fit-content; - align-items: center; - margin: 5px auto; - box-shadow: 0px 0px 10px lightgreen; - background-color: lightgreen; - border-radius: 8px; -} -.user-info * { - flex: 2 1 auto; - margin: 0; -} -.user-info .picture { - max-width: 3em; - max-height: 3em -} -.user-info a.logout { - border: 2px solid darkkhaki; - padding: 3px 6px; - text-decoration: none; - color: black; -} -.user-info a.logout:hover { - background-color: orange; -} -.debug-auth { - font-size: 90%; - background-color: #d8bebc75; - padding: 6px; -} -.debug-auth * { - margin: 0; -} -.debug-auth p { - border-bottom: 1px solid black; -} -.debug-auth ul { - padding: 0; - list-style: none; -} -.debug-auth p, .debug-auth .key { - font-weight: bold; -} -.content { - text-align: left; -} -.hasResponseStatus { - background-color: #88888840; -} -.hasResponseStatus.status-200 { - background-color: #00ff0040; -} -.hasResponseStatus.status-401 { - background-color: #ff000040; -} -.hasResponseStatus.status-403 { - background-color: #ff990040; -} -.hasResponseStatus.status-404 { - background-color: #ffCC0040; -} -.hasResponseStatus.status-503 { - background-color: #ffA88050; -} - -.role, .scope { - padding: 3px 6px; - margin: 3px; - border-radius: 6px; -} - -.role { - background-color: #44228840; -} - -.scope { - background-color: #8888FF80; -} - - -/* For home */ - -.login-box { - background-color: antiquewhite; - margin: 0.5em auto; - width: fit-content; - box-shadow: 0 0 10px #49759b88; - border-radius: 8px; -} -.login-box .description { - font-style: italic; - font-weight: bold; - background-color: #f7c7867d; - padding: 6px; - margin: 0; - border-radius: 8px 8px 0 0; -} -.providers { - justify-content: center; - padding: 0.8em; -} -.providers .provider { - min-height: 2em; -} -.providers .provider a.link { - text-decoration: none; - max-height: 2em; -} -.providers .provider .link div { - background-color: #f7c7867d; - border-radius: 8px; - padding: 6px; - text-align: center; - color: black; - font-weight: bold; - cursor: pointer; -} -.providers .provider .hint { - font-size: 80%; - max-width: 13em; -} -.providers .error { - padding: 3px 6px; - font-weight: bold; - flex: 1 1 auto; -} -.content .links-to-check { - display: flex; - justify-content: center; - gap: 0.5em; - flex-flow: wrap; -} -.content .links-to-check button { - color: black; - padding: 5px 10px; - text-decoration: none; - border-radius: 8px; - border: none; - cursor: pointer; -} -.content .links-to-check span { - margin: auto; -} - -.token { - overflow-wrap: anywhere; - font-family: monospace; -} - -.resource { - padding: 0.5em; - display: flex; - gap: 0.5em; - flex-direction: column; - width: fit-content; - align-items: center; - margin: 5px auto; - box-shadow: 0px 0px 10px #90c3eeA0; - background-color: #90c3eeA0; - border-radius: 8px; -} - -.resources { - display: flex; -} - -.resource { - text-align: center; -} - -.token-info { - margin: 0 1em; -} - -.key { - font-weight: bold; -} - -.token .key, .token .value { - display: inline; -} -.token .value { - padding-left: 1em; -} - -.msg { - text-align: center; - font-weight: bold; -} diff --git a/src/App.vue b/src/App.vue index 006f88b..40cefaf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,100 +1,40 @@ + + diff --git a/src/ResourceButton.vue b/src/ResourceButton.vue deleted file mode 100644 index 8c787cc..0000000 --- a/src/ResourceButton.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/src/ResourceResponse.vue b/src/ResourceResponse.vue deleted file mode 100644 index 63f9f24..0000000 --- a/src/ResourceResponse.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/src/TokenView.vue b/src/TokenView.vue deleted file mode 100644 index b8287bf..0000000 --- a/src/TokenView.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/src/UserInfo.vue b/src/UserInfo.vue deleted file mode 100644 index cfc1c31..0000000 --- a/src/UserInfo.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/src/main.ts b/src/main.ts index 459b1b7..48d8916 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,120 +1,37 @@ import { createApp } from 'vue' import Keycloak from "keycloak-js" -import VueKeycloakJs from '@dsb-norge/vue-keycloak-js' -import axios, { Axios, type AxiosInstance } from 'axios' -import App from '@/App.vue' +import VueKeycloakJs, { useKeycloak } from '@dsb-norge/vue-keycloak-js' +import axios from 'axios' +import App from './App.vue' -export interface Resource { - name: string - default_resource_id: string - role_required: string - scope_required: string -} +export const HTTP = axios.create({ + baseURL: '/', + timeout: 10_000 +}) -export interface Resources { - [name: string]: Resource -} - -interface ResourceProvider { - id: string - name: string - baseUrl: string - verifySSL: boolean - resources: Resources -} - -export interface ResourceProviders { - [name: string]: ResourceProvider -} - -interface Settings { - keycloakUri: string - realm: string - clientId: string - sso: boolean - resourceServerUrl: string - resourceScopes: string[] - authProvider: string - tokenSandbox: boolean - resourceProviders: ResourceProviders -} - -interface AxiosResourceProviders { - [name: string]: AxiosInstance -} - -export let settings: Settings -export let authServer: AxiosInstance -export let resourceServer: AxiosInstance -export let axiosResourceProviders: AxiosResourceProviders = {} - -// The settings.json file is expected at the server's base url -axios.get("settings.json").then().then( - resp => { - settings = resp.data - resourceServer = axios.create({ - baseURL: settings.resourceServerUrl, - timeout: 10000 - }) - authServer = axios.create({ - baseURL: '/', - timeout: 10000 - }) - app.use(VueKeycloakJs, { - config: { - url: settings.keycloakUri, - realm: settings.realm, - clientId: settings.clientId - }, - init: { - onLoad: settings.sso ? "check-sso" : "login-required", - scope: "openid email profile " + settings.resourceScopes.join(" ") - }, - onReady(keycloak: Keycloak) { - initializeTokenInterceptor(keycloak) - app.mount("#app") - }, - }) - } -) - -function initializeTokenInterceptor(keycloak: Keycloak) { - Object.entries(settings.resourceProviders).forEach( - ([id, resourceProvider]) => { - const rp = axios.create({ - baseURL: resourceProvider.baseUrl, - timeout: 10000 - }) - rp.interceptors.request.use(axiosSettings => { - if (keycloak.authenticated) { - axiosSettings.headers.Authorization = `Bearer ${keycloak.token}` - axiosSettings.headers.auth_provider = settings.authProvider - } - return axiosSettings - }, error => { - return Promise.reject(error) - }) - axiosResourceProviders[id] = rp - } - ) - authServer.interceptors.request.use(axiosSettings => { +function initializeTokenInterceptor() { + HTTP.interceptors.request.use(config => { + const keycloak = useKeycloak() if (keycloak.authenticated) { - axiosSettings.headers.Authorization = `Bearer ${keycloak.token}` - axiosSettings.headers.auth_provider = settings.authProvider + config.headers.Authorization = `Bearer ${keycloak.token}` } - return axiosSettings - }, error => { - return Promise.reject(error) - }) - resourceServer.interceptors.request.use(axiosSettings => { - if (keycloak.authenticated) { - axiosSettings.headers.Authorization = `Bearer ${keycloak.token}` - axiosSettings.headers.auth_provider = settings.authProvider - } - return axiosSettings + return config }, error => { return Promise.reject(error) }) } -const app = createApp(App) +createApp(App) + .use(VueKeycloakJs, { + config: { + url: 'https://philo.ydns.eu/auth/', + realm: 'test', + clientId: 'oidc-test-web', + }, + onReady(kk: Keycloak, vkk) { + //console.log(kk, vkk) + console.log(kk.idTokenParsed) + initializeTokenInterceptor() + }, + }) + .mount('#app') diff --git a/tsconfig.app.json b/tsconfig.app.json index 8bd162f..913b8f2 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,20 +1,12 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": [ - "env.d.ts", - "src/**/*", - "src/**/*.vue", - "config.ts" - ], - "exclude": [ - "src/**/__tests__/*" - ], + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] } } } diff --git a/tsconfig.node.json b/tsconfig.node.json deleted file mode 100644 index 50976ed..0000000 --- a/tsconfig.node.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@tsconfig/node22/tsconfig.json", - "include": [ - "vite.config.*", - "vitest.config.*", - "cypress.config.*", - "nightwatch.conf.*", - "playwright.config.*" - ], - "compilerOptions": { - "noEmit": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "module": "ESNext", - "moduleResolution": "Bundler", - "types": [ - "node", - "vite/client" - ] - } -} diff --git a/vite.config.ts b/vite.config.ts index 9b20b48..2e3ef22 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,44 +1,29 @@ -// -// Webcrypto is required for docoding the tokens, and thus https is compulsary. -// -// Read an environment file (.env) at the project's root, -// with 2 variables VITE_SSL_KEY and VITE_SSL_CERT defining the location -// of the ssl key and cert -// - import { fileURLToPath, URL } from 'node:url' -import { defineConfig, UserConfig, loadEnv } from 'vite' +import { defineConfig } from 'vite' import fs from 'fs'; import path from 'path'; import vue from '@vitejs/plugin-vue' -//import vueDevTools from 'vite-plugin-vue-devtools' +import vueDevTools from 'vite-plugin-vue-devtools' -// Read the env file -const env = loadEnv("dev", ".") - -let baseSettings: UserConfig = { +// https://vite.dev/config/ +export default defineConfig({ plugins: [ vue(), - //vueDevTools(), + vueDevTools(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }, }, -} - -if (env.VITE_SSL_CERT && env.VITE_SSL_KEY) { - baseSettings['server'] = { - hmr: false, + server: { https: { - key: fs.readFileSync(path.resolve(env.VITE_SSL_KEY)), - cert: fs.readFileSync(path.resolve(env.VITE_SSL_CERT)) - } + // key: fs.readFileSync(path.resolve(__dirname, 'localhost-key.pem')), + // cert: fs.readFileSync(path.resolve(__dirname, 'localhost.pem')), + key: fs.readFileSync(path.resolve("/home/phil", 'tiptop+4-key.pem')), + cert: fs.readFileSync(path.resolve("/home/phil", 'tiptop+4.pem')), + }, } -} - -// https://vite.dev/config/ -export default defineConfig(baseSettings) +})