diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 53c75fe..5a0bcc2 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -3,7 +3,7 @@ on: workflow_dispatch: inputs: build: - description: "Build package and container" + description: "Build package" required: true default: false type: boolean @@ -70,40 +70,3 @@ jobs: - 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/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..77693ed 100644 --- a/README.md +++ b/README.md @@ -2,123 +2,24 @@ 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), +It is a sibbling of the server version (oidc-test)[philorg/oidc-fastapi-test], which acts also as a resource server. -Live demo: : - +Live demo: https://philo.ydns.eu/oidc-test-web: - 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). +## Deployment -## 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 { reverse-proxy hostname.domainame:8874 diff --git a/package.json b/package.json index 360dd30..ee57e60 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "oidc-test-web", + "name": "oidc-text-web", "version": "0.0.0", "type": "module", "scripts": { diff --git a/public/styles.css b/public/styles.css index 8d4804f..17d7c4c 100644 --- a/public/styles.css +++ b/public/styles.css @@ -2,15 +2,11 @@ body { font-family: Arial, Helvetica, sans-serif; background-color: floralwhite; margin: 0; - font-family: system-ui; - text-align: center; } h1 { + text-align: center; 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; @@ -24,9 +20,6 @@ hr { .center { text-align: center; } -.error { - color: darkred; -} .content { width: 100%; display: flex; @@ -58,6 +51,7 @@ hr { border: 2px solid darkkhaki; padding: 3px 6px; text-decoration: none; + text-align: center; color: black; } .user-info a.logout:hover { @@ -72,6 +66,7 @@ hr { margin: 0; } .debug-auth p { + text-align: center; border-bottom: 1px solid black; } .debug-auth ul { @@ -102,25 +97,15 @@ hr { .hasResponseStatus.status-503 { background-color: #ffA88050; } - -.role, .scope { - padding: 3px 6px; - margin: 3px; - border-radius: 6px; -} - .role { + padding: 3px 6px; background-color: #44228840; } -.scope { - background-color: #8888FF80; -} - - /* For home */ .login-box { + text-align: center; background-color: antiquewhite; margin: 0.5em auto; width: fit-content; @@ -147,6 +132,7 @@ hr { max-height: 2em; } .providers .provider .link div { + text-align: center; background-color: #f7c7867d; border-radius: 8px; padding: 6px; @@ -161,25 +147,22 @@ hr { } .providers .error { padding: 3px 6px; + text-align: center; font-weight: bold; flex: 1 1 auto; } .content .links-to-check { display: flex; + text-align: center; justify-content: center; gap: 0.5em; flex-flow: wrap; } -.content .links-to-check button { +.content .links-to-check a { color: black; padding: 5px 10px; text-decoration: none; border-radius: 8px; - border: none; - cursor: pointer; -} -.content .links-to-check span { - margin: auto; } .token { @@ -187,6 +170,12 @@ hr { font-family: monospace; } +.actions { + display: flex; + justify-content: center; + gap: 0.5em; +} + .resource { padding: 0.5em; display: flex; @@ -195,8 +184,8 @@ hr { width: fit-content; align-items: center; margin: 5px auto; - box-shadow: 0px 0px 10px #90c3eeA0; - background-color: #90c3eeA0; + box-shadow: 0px 0px 10px #90c3ee; + background-color: #90c3ee; border-radius: 8px; } @@ -208,7 +197,11 @@ hr { text-align: center; } -.token-info { +.error { + color: darkred; +} + +.from-keycloak-vue { margin: 0 1em; } diff --git a/src/App.vue b/src/App.vue index 006f88b..fc161f9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,58 +1,40 @@ @@ -60,41 +42,70 @@ async function getResource(evt: MouseEvent, resourceName: string, resource: Reso 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..427388e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,31 +1,8 @@ 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' - -export interface Resource { - name: string - default_resource_id: string - role_required: string - scope_required: string -} - -export interface Resources { - [name: string]: Resource -} - -interface ResourceProvider { - id: string - name: string - baseUrl: string - verifySSL: boolean - resources: Resources -} - -export interface ResourceProviders { - [name: string]: ResourceProvider -} +import axios, { type AxiosInstance } from 'axios' +import App from './App.vue' interface Settings { keycloakUri: string @@ -36,17 +13,12 @@ interface Settings { 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( @@ -72,31 +44,14 @@ axios.get("settings.json").then().then( }, onReady(keycloak: Keycloak) { initializeTokenInterceptor(keycloak) - app.mount("#app") }, }) + 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 => { if (keycloak.authenticated) { axiosSettings.headers.Authorization = `Bearer ${keycloak.token}`