Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
X-Permitted-Cross-Domain-Policies "none"
Referrer-Policy "no-referrer"
}

header /assets/auth-config.json {
Cache-Control "no-store"
}
}
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
12 changes: 12 additions & 0 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
@@ -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"}
]
27 changes: 27 additions & 0 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
37 changes: 23 additions & 14 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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({
Expand Down
32 changes: 31 additions & 1 deletion src/app/app.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,40 @@
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;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
}

.login-content {
flex: 1;
overflow-y: auto;
}

@keyframes fadeSlide {
from {
opacity: 0;
Expand All @@ -102,6 +129,9 @@
.tag-subtitle {
font-size: 11px;
}
.current-user {
display: none;
}
.logo,
.logo-icon {
opacity: 0;
Expand All @@ -110,4 +140,4 @@
margin: 0;
overflow: hidden;
}
}
}
30 changes: 22 additions & 8 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<mat-toolbar class="mat-elevation-z2 navbar">
<button mat-icon-button (click)="toggleMenu()" class="menu-btn">
<button mat-icon-button (click)="toggleMenu()" class="menu-btn" *ngIf="!isLoginPage">
<mat-icon>menu</mat-icon>
</button>
<a routerLink="/" class="logo">
Expand All @@ -16,12 +16,26 @@
</div>
<div class="dummy"></div>
</div>
<span class="toolbar-spacer"></span>
<div class="auth-actions" *ngIf="isAuthenticated">
<span class="current-user">{{ currentUser }}</span>
<button mat-icon-button (click)="logout()" title="Logout" aria-label="Logout">
<mat-icon>logout</mat-icon>
</button>
</div>
</mat-toolbar>
<mat-sidenav-container class="sidenav-container">
<mat-sidenav mode="side" opened class="sidenav" [style.width]="sidenavWidth">
<app-sidenav-buttons></app-sidenav-buttons>
</mat-sidenav>
<mat-sidenav-content class="content" [style.margin-left]="sidenavWidth">
<ng-container *ngIf="isLoginPage; else dsommLayout">
<main class="login-content">
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
</main>
</ng-container>
<ng-template #dsommLayout>
<mat-sidenav-container class="sidenav-container">
<mat-sidenav mode="side" opened class="sidenav" [style.width]="sidenavWidth">
<app-sidenav-buttons></app-sidenav-buttons>
</mat-sidenav>
<mat-sidenav-content class="content" [style.margin-left]="sidenavWidth">
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
</ng-template>
35 changes: 33 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { NavigationEnd, Router } from '@angular/router';
import { filter, Subject, takeUntil } from 'rxjs';
import { ThemeService } from './service/theme.service';
import { TitleService } from './service/title.service';
import { AuthService } from './services/auth.service';

@Component({
selector: 'app-root',
Expand All @@ -14,10 +16,16 @@ export class AppComponent implements OnInit, OnDestroy {
subtitle = '';
menuIsOpen: boolean = true;
sidenavWidth: string = '250px';
isLoginPage = false;

private destroy$ = new Subject<void>();

constructor(private themeService: ThemeService, private titleService: TitleService) {
constructor(
private themeService: ThemeService,
private titleService: TitleService,
private authService: AuthService,
private router: Router
) {
this.themeService.initTheme();
}

Expand All @@ -37,6 +45,16 @@ export class AppComponent implements OnInit, OnDestroy {
this.title = titleInfo?.dimension || '';
this.subtitle = titleInfo?.level ? 'Level ' + titleInfo?.level : '';
});

this.isLoginPage = this.router.url.split('?')[0] === '/login';
this.router.events
.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
takeUntil(this.destroy$)
)
.subscribe(event => {
this.isLoginPage = event.urlAfterRedirects.split('?')[0] === '/login';
});
}

ngOnDestroy(): void {
Expand All @@ -49,4 +67,17 @@ export class AppComponent implements OnInit, OnDestroy {
this.sidenavWidth = this.menuIsOpen ? '250px' : '0px';
localStorage.setItem('state.menuIsOpen', this.menuIsOpen.toString());
}

get isAuthenticated(): boolean {
return this.authService.isAuthenticated();
}

get currentUser(): string | null {
return this.authService.getCurrentUser();
}

logout(): void {
this.authService.logout();
void this.router.navigate(['/login']);
}
}
10 changes: 9 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgModule } from '@angular/core';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { MatToolbarModule } from '@angular/material/toolbar';
Expand Down Expand Up @@ -40,6 +40,12 @@ import { ColResizeDirective } from './directive/col-resize.directive';
import { AddEvidenceModalComponent } from './component/add-evidence-modal/add-evidence-modal.component';
import { EvidencePanelComponent } from './component/evidence-panel/evidence-panel.component';
import { ViewEvidenceModalComponent } from './component/view-evidence-modal/view-evidence-modal.component';
import { LoginComponent } from './pages/login/login.component';
import { AuthService } from './services/auth.service';

export function initializeAuth(authService: AuthService): () => Promise<void> {
return () => authService.loadConfig();
}

@NgModule({
declarations: [
Expand Down Expand Up @@ -71,6 +77,7 @@ import { ViewEvidenceModalComponent } from './component/view-evidence-modal/view
AddEvidenceModalComponent,
EvidencePanelComponent,
ViewEvidenceModalComponent,
LoginComponent,
],
imports: [
BrowserModule,
Expand All @@ -89,6 +96,7 @@ import { ViewEvidenceModalComponent } from './component/view-evidence-modal/view
providers: [
LoaderService,
ModalMessageComponent,
{ provide: APP_INITIALIZER, useFactory: initializeAuth, deps: [AuthService], multi: true },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: { close: (dialogResult: any) => {} } },
],
Expand Down
Loading