diff --git a/Caddyfile b/Caddyfile index f28b4dda..e88a629a 100644 --- a/Caddyfile +++ b/Caddyfile @@ -17,4 +17,8 @@ X-Permitted-Cross-Domain-Policies "none" Referrer-Policy "no-referrer" } + + header /assets/auth-config.json { + Cache-Control "no-store" + } } diff --git a/Dockerfile b/Dockerfile index 72032813..4ed073cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,5 +25,13 @@ FROM caddy:2.10.2 ENV PORT=8080 COPY Caddyfile /etc/caddy/Caddyfile +COPY docker/docker-entrypoint.sh /usr/local/bin/dsomm-entrypoint COPY --from=build ["/usr/src/app/dist/dsomm/", "/srv"] COPY --from=yaml ["/var/www/html/generated/model.yaml", "/srv/assets/YAML/default/model.yaml"] + +RUN chmod +x /usr/local/bin/dsomm-entrypoint \ + && mkdir -p /srv/assets \ + && chmod -R u+rwX,go+rX /srv/assets + +ENTRYPOINT ["/usr/local/bin/dsomm-entrypoint"] +CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/README.md b/README.md index 40c908d7..7f27bcaa 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,54 @@ You can switch on to show open TODO's for evidence by changing IS_SHOW_EVIDENCE_ This page uses the Browser's localStorage to store the state of the circular headmap. +# Static Demo Authentication + +This Angular frontend includes simple runtime-configured authentication for demo and internal +deployments. All users have the same permissions. + +For local development, default demo credentials are defined in `src/assets/auth-config.json`: + +| Username | Password | +| --- | --- | +| `admin` | `dsomm-admin` | +| `auditor` | `dsomm-audit` | +| `developer` | `dsomm-dev` | +| `viewer` | `dsomm-view` | + +For Docker deployments, define users at container startup instead of rebuilding the image: + +```yaml +services: + dsomm: + image: wurstbrot/dsomm:latest + ports: + - "8080:8080" + environment: + DSOMM_AUTH_USERS: >- + [ + {"username":"admin","password":"change-me"}, + {"username":"auditor","password":"audit-me"} + ] +``` + +The container entrypoint writes `DSOMM_AUTH_USERS` to `/srv/assets/auth-config.json`. You can also +mount your own config file at `/srv/assets/auth-config.json` with this shape: + +```json +{ + "users": [ + { "username": "admin", "password": "change-me" } + ] +} +``` + +Sign in at `/login`. The app stores the current user in the browser's `sessionStorage`, so the +login lasts only for the current browser session. + +Security warning: this is frontend-only authentication. It is not secure for production because +the browser must receive the auth config and credentials can be inspected by users. Use a backend +identity provider or server-side access control for production deployments. + # Changes Changes to the application are displayed at the release page of [DevSecOps-MaturityModel](https://github.com/devsecopsmaturitymodel/DevSecOps-MaturityModel-data/releases). diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 00000000..eab2c2bf --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,12 @@ +services: + dsomm: + build: . + ports: + - "8080:8080" + environment: + DSOMM_AUTH_USERS: >- + [ + {"username":"admin","password":"change-me"}, + {"username":"auditor","password":"audit-me"}, + {"username":"developer","password":"dev-me"} + ] diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 00000000..7f0ecb6d --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -eu + +AUTH_CONFIG_PATH="${DSOMM_AUTH_CONFIG_PATH:-/srv/assets/auth-config.json}" +AUTH_CONFIG_DIR="$(dirname "$AUTH_CONFIG_PATH")" + +write_auth_config() { + tmp_config="${AUTH_CONFIG_PATH}.tmp.$$" + + { + printf '{\n "users": ' + printf '%s' "$1" + printf '\n}\n' + } > "$tmp_config" + + mv "$tmp_config" "$AUTH_CONFIG_PATH" +} + +if [ -n "${DSOMM_AUTH_USERS:-}" ]; then + mkdir -p "$AUTH_CONFIG_DIR" + write_auth_config "$DSOMM_AUTH_USERS" +elif [ ! -f "$AUTH_CONFIG_PATH" ]; then + mkdir -p "$AUTH_CONFIG_DIR" + printf '{\n "users": []\n}\n' > "$AUTH_CONFIG_PATH" +fi + +exec "$@" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index fe90fbf0..25a12dbf 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,4 +1,4 @@ -import { Component, NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AboutUsComponent } from './pages/about-us/about-us.component'; import { UserdayComponent } from './pages/userday/userday.component'; @@ -11,21 +11,30 @@ import { TeamsComponent } from './pages/teams/teams.component'; import { RoadmapComponent } from './pages/roadmap/roadmap.component'; import { SettingsComponent } from './pages/settings/settings.component'; import { ReportComponent } from './pages/report/report.component'; +import { AuthGuard } from './guards/auth.guard'; +import { LoginComponent } from './pages/login/login.component'; const routes: Routes = [ - { path: '', component: CircularHeatmapComponent }, - { path: 'circular-heatmap', component: CircularHeatmapComponent }, - { path: 'matrix', component: MatrixComponent }, - { path: 'activity-description', component: ActivityDescriptionPageComponent }, - { path: 'mapping', component: MappingComponent }, - { path: 'usage', redirectTo: 'usage/' }, - { path: 'usage/:page', component: UsageComponent }, - { path: 'teams', component: TeamsComponent }, - { path: 'about', component: AboutUsComponent }, - { path: 'userday', component: UserdayComponent }, - { path: 'roadmap', component: RoadmapComponent }, - { path: 'settings', component: SettingsComponent }, - { path: 'report', component: ReportComponent }, + { path: 'login', component: LoginComponent, canActivate: [AuthGuard] }, + { + path: '', + canActivateChild: [AuthGuard], + children: [ + { path: '', component: CircularHeatmapComponent }, + { path: 'circular-heatmap', component: CircularHeatmapComponent }, + { path: 'matrix', component: MatrixComponent }, + { path: 'activity-description', component: ActivityDescriptionPageComponent }, + { path: 'mapping', component: MappingComponent }, + { path: 'usage', redirectTo: 'usage/' }, + { path: 'usage/:page', component: UsageComponent }, + { path: 'teams', component: TeamsComponent }, + { path: 'about', component: AboutUsComponent }, + { path: 'userday', component: UserdayComponent }, + { path: 'roadmap', component: RoadmapComponent }, + { path: 'settings', component: SettingsComponent }, + { path: 'report', component: ReportComponent }, + ], + }, ]; @NgModule({ diff --git a/src/app/app.component.css b/src/app/app.component.css index d9d39268..2099b4d9 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -75,6 +75,27 @@ transform: scale(1.05); } +.toolbar-spacer { + flex: 1 1 auto; +} + +.auth-actions { + position: relative; + z-index: 1; + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; +} + +.current-user { + font-size: 14px; + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .content { padding: 24px; animation: fadeSlide 1s ease; @@ -82,6 +103,12 @@ box-sizing: border-box; overflow-y: auto; } + +.login-content { + flex: 1; + overflow-y: auto; +} + @keyframes fadeSlide { from { opacity: 0; @@ -102,6 +129,9 @@ .tag-subtitle { font-size: 11px; } + .current-user { + display: none; + } .logo, .logo-icon { opacity: 0; @@ -110,4 +140,4 @@ margin: 0; overflow: hidden; } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 3cbe51e5..f2962848 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1,5 @@ -