AI Implementation feature(820): Project Scaffold and Platform Foundations (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-07-21 14:23:53 +00:00
parent e55c9cda56
commit 03bcb6b156
131 changed files with 23657 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
import { Component, OnInit, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { BootstrapService } from './core/services/bootstrap.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
template: `<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit {
private bootstrap = inject(BootstrapService);
async ngOnInit() {
await this.bootstrap.load();
}
}
+19
View File
@@ -0,0 +1,19 @@
import { Routes } from '@angular/router';
import { authGuard } from './core/guards/auth.guard';
export const APP_ROUTES: Routes = [
{
path: 'bootstrap',
loadComponent: () => import('./features/bootstrap/create-admin.component').then((m) => m.CreateAdminComponent),
},
{
path: 'login',
loadComponent: () => import('./features/auth/login.component').then((m) => m.LoginComponent),
},
{
path: '',
loadComponent: () => import('./features/home/home.component').then((m) => m.HomeComponent),
canActivate: [authGuard],
},
{ path: '**', redirectTo: '' },
];
@@ -0,0 +1,18 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { BootstrapService } from '../services/bootstrap.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const bootstrap = inject(BootstrapService);
const router = inject(Router);
if (!bootstrap.initialized()) {
return router.createUrlTree(['/bootstrap']);
}
if (!auth.isAuthenticated()) {
return router.createUrlTree(['/login']);
}
return true;
};
@@ -0,0 +1,12 @@
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = auth.getAccessToken();
if (token) {
req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
}
return next(req);
};
@@ -0,0 +1,23 @@
import { HttpInterceptorFn } from '@angular/common/http';
function readCookie(name: string): string | null {
const header = document.cookie;
if (!header) return null;
for (const part of header.split(';')) {
const [k, ...v] = part.trim().split('=');
if (k === name) return v.join('=');
}
return null;
}
const UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
export const csrfInterceptor: HttpInterceptorFn = (req, next) => {
if (UNSAFE.has(req.method)) {
const token = readCookie('csrf');
if (token) {
req = req.clone({ setHeaders: { 'X-CSRF-Token': token } });
}
}
return next(req);
};
@@ -0,0 +1,30 @@
import { Injectable, signal, computed } from '@angular/core';
export interface CurrentUser {
id: string;
username: string;
role: 'admin' | 'player';
}
@Injectable({ providedIn: 'root' })
export class AuthService {
private accessToken = signal<string | null>(null);
private user = signal<CurrentUser | null>(null);
readonly currentUser = computed(() => this.user());
readonly isAuthenticated = computed(() => !!this.user());
setSession(token: string, user: CurrentUser): void {
this.accessToken.set(token);
this.user.set(user);
}
clear(): void {
this.accessToken.set(null);
this.user.set(null);
}
getAccessToken(): string | null {
return this.accessToken();
}
}
@@ -0,0 +1,49 @@
import { Injectable, signal } from '@angular/core';
export interface BootstrapPayload {
initialized: boolean;
pageTitle: string;
logo: string;
welcomeMarkdown: string;
theme: any;
defaultChallengeIp: string;
registrationsEnabled: boolean;
}
@Injectable({ providedIn: 'root' })
export class BootstrapService {
readonly payload = signal<BootstrapPayload | null>(null);
readonly initialized = signal<boolean>(false);
async load(): Promise<BootstrapPayload | null> {
try {
const res = await fetch('/api/v1/bootstrap', { credentials: 'include' });
const data = (await res.json()) as BootstrapPayload;
this.payload.set(data);
this.initialized.set(data.initialized);
this.applyTheme(data.theme);
return data;
} catch (e) {
console.error('Bootstrap load failed', e);
return null;
}
}
private applyTheme(theme: any): void {
if (!theme?.tokens) return;
const root = document.documentElement.style;
const t = theme.tokens;
root.setProperty('--color-primary', t.primary);
root.setProperty('--color-secondary', t.secondary);
root.setProperty('--color-accent', t.accent);
root.setProperty('--color-surface', t.surface);
root.setProperty('--color-text', t.text);
root.setProperty('--color-success', t.success);
root.setProperty('--color-warning', t.warning);
root.setProperty('--color-danger', t.danger);
root.setProperty('--font-family', t.fontFamily);
root.setProperty('--radius-sm', t.radii.sm);
root.setProperty('--radius-md', t.radii.md);
root.setProperty('--radius-lg', t.radii.lg);
}
}
@@ -0,0 +1,49 @@
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../../core/services/auth.service';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-login',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<div class="card">
<h1>Sign in</h1>
<label>Username<input [(ngModel)]="username" name="u" /></label>
<label>Password<input [(ngModel)]="password" name="p" type="password" /></label>
<p style="color:var(--color-danger)">{{ error }}</p>
<button (click)="submit()" [disabled]="loading">{{ loading ? 'Signing in...' : 'Sign in' }}</button>
</div>
</div>
`,
})
export class LoginComponent {
private http = inject(HttpClient);
private auth = inject(AuthService);
private router = inject(Router);
username = '';
password = '';
loading = false;
error = '';
async submit(): Promise<void> {
this.loading = true;
this.error = '';
try {
await fetch('/api/v1/auth/csrf', { credentials: 'include' });
const res: any = await this.http
.post('/api/v1/auth/login', { username: this.username, password: this.password }, { withCredentials: true })
.toPromise();
this.auth.setSession(res.accessToken, res.user);
await this.router.navigateByUrl('/');
} catch (e: any) {
this.error = e?.error?.message ?? 'Failed';
} finally {
this.loading = false;
}
}
}
@@ -0,0 +1,50 @@
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { AuthService } from '../../core/services/auth.service';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-create-admin',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<div class="card">
<h1>Create the first admin</h1>
<p>This HIPCTF instance has not been initialized.</p>
<label>Username<input [(ngModel)]="username" name="username" /></label>
<label>Password<input [(ngModel)]="password" name="password" type="password" /></label>
<p style="color:var(--color-danger)">{{ error }}</p>
<button (click)="submit()" [disabled]="loading">{{ loading ? 'Creating...' : 'Create admin' }}</button>
</div>
</div>
`,
})
export class CreateAdminComponent {
private http = inject(HttpClient);
private auth = inject(AuthService);
private router = inject(Router);
username = '';
password = '';
loading = false;
error = '';
async submit(): Promise<void> {
this.loading = true;
this.error = '';
try {
await fetch('/api/v1/auth/csrf', { credentials: 'include' });
const res: any = await this.http
.post('/api/v1/auth/register-first-admin', { username: this.username, password: this.password }, { withCredentials: true })
.toPromise();
this.auth.setSession(res.accessToken, res.user);
await this.router.navigateByUrl('/');
} catch (e: any) {
this.error = e?.error?.message ?? 'Failed';
} finally {
this.loading = false;
}
}
}
@@ -0,0 +1,26 @@
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BootstrapService } from '../../core/services/bootstrap.service';
import { AuthService } from '../../core/services/auth.service';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule],
template: `
<div class="container">
<h1>{{ bootstrap.payload()?.pageTitle ?? 'HIPCTF' }}</h1>
<div *ngIf="!auth.isAuthenticated()">
<p>{{ bootstrap.payload()?.welcomeMarkdown }}</p>
<a href="/login">Sign in</a>
</div>
<div *ngIf="auth.isAuthenticated()">
<p>Signed in as <b>{{ auth.currentUser()?.username }}</b> ({{ auth.currentUser()?.role }})</p>
</div>
</div>
`,
})
export class HomeComponent {
bootstrap = inject(BootstrapService);
auth = inject(AuthService);
}