Skip to content
Merged
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
64 changes: 26 additions & 38 deletions .github/workflows/firebase-hosting-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,56 @@
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge

on:
push:
branches:
- master

permissions:
contents: read

jobs:
build_and_deploy:
runs-on: ubuntu-latest
env:
APP_TITLE: ${{ secrets.APP_TITLE }}
APP_API_URL: ${{ secrets.APP_API_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPEN_WEATHER_MAP_API_KEY: ${{ OPEN_WEATHER_MAP_API_KEY }}
# firebase config
FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }}
FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }}
FIREBASE_STORAGE_BUCKET: ${{ secrets.FIREBASE_STORAGE_BUCKET }}
FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.FIREBASE_MESSAGING_SENDER_ID }}
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
FIREBASE_MEASUREMENT_ID: ${{ secrets.FIREBASE_MEASUREMENT_ID }}
APP_TITLE: ${{ vars.APP_TITLE || secrets.APP_TITLE }}
APP_API_URL: ${{ vars.APP_API_URL || vars.API_URL || secrets.APP_API_URL || secrets.API_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY || vars.OPENAI_API_KEY }}
OPEN_WEATHER_MAP_API_KEY: ${{ secrets.OPEN_WEATHER_MAP_API_KEY || vars.OPEN_WEATHER_MAP_API_KEY }}
FIREBASE_API_KEY: ${{ vars.FIREBASE_API_KEY || secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_DOMAIN: ${{ vars.FIREBASE_AUTH_DOMAIN || secrets.FIREBASE_AUTH_DOMAIN }}
FIREBASE_DATABASE_URL: ${{ vars.FIREBASE_DATABASE_URL || secrets.FIREBASE_DATABASE_URL }}
FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID }}
FIREBASE_STORAGE_BUCKET: ${{ vars.FIREBASE_STORAGE_BUCKET || secrets.FIREBASE_STORAGE_BUCKET }}
FIREBASE_MESSAGING_SENDER_ID: ${{ vars.FIREBASE_MESSAGING_SENDER_ID || secrets.FIREBASE_MESSAGING_SENDER_ID }}
FIREBASE_APP_ID: ${{ vars.FIREBASE_APP_ID || secrets.FIREBASE_APP_ID }}
FIREBASE_MEASUREMENT_ID: ${{ vars.FIREBASE_MEASUREMENT_ID || secrets.FIREBASE_MEASUREMENT_ID }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 20
cache: npm

- name: Install dependencies
run: npm install
run: npm ci

- name: Generate Angular environment file
run: |
cat <<EOF > src/environments/environment.ts
export const environment = {
production: true,
title: '${APP_TITLE}',
apiUrl: '${APP_API_URL}',
openAiApiKey: '${OPENAI_API_KEY}',
openWeatherMapApiKey: '${OPEN_WEATHER_MAP_API_KEY}',
firebaseConfig: {
apiKey: "${FIREBASE_API_KEY}",
authDomain: "${FIREBASE_AUTH_DOMAIN}",
projectId: "${FIREBASE_PROJECT_ID}",
storageBucket: "${FIREBASE_STORAGE_BUCKET}",
messagingSenderId: "${FIREBASE_MESSAGING_SENDER_ID}",
appId: "${FIREBASE_APP_ID}",
measurementId: "${FIREBASE_MEASUREMENT_ID}"
}
};
EOF
- name: Generate Angular environment files
run: npm run generate:env

- name: Build Angular app
run: npm run build
- uses: actions/checkout@v4
- run: npm ci
- uses: FirebaseExtended/action-hosting-deploy@v0

- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_COLINMICHAELS }}
channelId: live
projectId: colinmichaels
projectId: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID || 'colinmichaels' }}
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
21 changes: 20 additions & 1 deletion .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,32 @@ jobs:
build_and_preview:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
env:
APP_TITLE: ${{ vars.APP_TITLE || secrets.APP_TITLE }}
APP_API_URL: ${{ vars.APP_API_URL || vars.API_URL || secrets.APP_API_URL || secrets.API_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY || vars.OPENAI_API_KEY }}
OPEN_WEATHER_MAP_API_KEY: ${{ secrets.OPEN_WEATHER_MAP_API_KEY || vars.OPEN_WEATHER_MAP_API_KEY }}
FIREBASE_API_KEY: ${{ vars.FIREBASE_API_KEY || secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_DOMAIN: ${{ vars.FIREBASE_AUTH_DOMAIN || secrets.FIREBASE_AUTH_DOMAIN }}
FIREBASE_DATABASE_URL: ${{ vars.FIREBASE_DATABASE_URL || secrets.FIREBASE_DATABASE_URL }}
FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID }}
FIREBASE_STORAGE_BUCKET: ${{ vars.FIREBASE_STORAGE_BUCKET || secrets.FIREBASE_STORAGE_BUCKET }}
FIREBASE_MESSAGING_SENDER_ID: ${{ vars.FIREBASE_MESSAGING_SENDER_ID || secrets.FIREBASE_MESSAGING_SENDER_ID }}
FIREBASE_APP_ID: ${{ vars.FIREBASE_APP_ID || secrets.FIREBASE_APP_ID }}
FIREBASE_MEASUREMENT_ID: ${{ vars.FIREBASE_MEASUREMENT_ID || secrets.FIREBASE_MEASUREMENT_ID }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run generate:env
- run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_COLINMICHAELS }}
projectId: colinmichaels
projectId: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID || 'colinmichaels' }}
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
81 changes: 45 additions & 36 deletions .github/workflows/firebase_deployment_workflow.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,52 @@
name: Deploy to Production
name: Manual Firebase Deploy

on:
push:
branches: [ main ]
workflow_dispatch:

permissions:
contents: read

jobs:
deploy:
runs-on: ubuntu-latest

env:
APP_TITLE: ${{ vars.APP_TITLE || secrets.APP_TITLE }}
APP_API_URL: ${{ vars.APP_API_URL || vars.API_URL || secrets.APP_API_URL || secrets.API_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY || vars.OPENAI_API_KEY }}
OPEN_WEATHER_MAP_API_KEY: ${{ secrets.OPEN_WEATHER_MAP_API_KEY || vars.OPEN_WEATHER_MAP_API_KEY }}
FIREBASE_API_KEY: ${{ vars.FIREBASE_API_KEY || secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_DOMAIN: ${{ vars.FIREBASE_AUTH_DOMAIN || secrets.FIREBASE_AUTH_DOMAIN }}
FIREBASE_DATABASE_URL: ${{ vars.FIREBASE_DATABASE_URL || secrets.FIREBASE_DATABASE_URL }}
FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID }}
FIREBASE_STORAGE_BUCKET: ${{ vars.FIREBASE_STORAGE_BUCKET || secrets.FIREBASE_STORAGE_BUCKET }}
FIREBASE_MESSAGING_SENDER_ID: ${{ vars.FIREBASE_MESSAGING_SENDER_ID || secrets.FIREBASE_MESSAGING_SENDER_ID }}
FIREBASE_APP_ID: ${{ vars.FIREBASE_APP_ID || secrets.FIREBASE_APP_ID }}
FIREBASE_MEASUREMENT_ID: ${{ vars.FIREBASE_MEASUREMENT_ID || secrets.FIREBASE_MEASUREMENT_ID }}
steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build:prod
env:
APP_TITLE: ${{ secrets.APP_TITLE }}
API_URL: ${{ secrets.API_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPEN_WEATHER_MAP_API_KEY: ${{ secrets.OPEN_WEATHER_MAP_API_KEY }}
FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }}
FIREBASE_DATABASE_URL: ${{ secrets.FIREBASE_DATABASE_URL }}
FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }}
FIREBASE_STORAGE_BUCKET: ${{ secrets.FIREBASE_STORAGE_BUCKET }}
FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.FIREBASE_MESSAGING_SENDER_ID }}
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
FIREBASE_MEASUREMENT_ID: ${{ secrets.FIREBASE_MEASUREMENT_ID }}

- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: ${{ secrets.FIREBASE_PROJECT_ID }}
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Generate Angular environment files
run: npm run generate:env

- name: Build
run: npm run build

- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_COLINMICHAELS || secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: ${{ vars.FIREBASE_PROJECT_ID || secrets.FIREBASE_PROJECT_ID || 'colinmichaels' }}
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ Thumbs.db
# Environment files
.env
src/environments/environment.local.ts

src/environments/.env.*
!src/environments/.env.example

1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
65 changes: 65 additions & 0 deletions docs/ARCHITECTURE/OVERVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Architecture Overview

## Runtime Shape

The app uses Angular standalone components with route-driven screens and service-centric state.

- Router controls entry screens (home, login, desktop, boot, sleep).
- Desktop screen coordinates window lifecycle and system UI.
- Services hold long-lived state (apps, user, settings, storage, CLI, sound, files, notifications).
- Dynamic component loading is used for in-window apps.

## Major Subsystems

- Desktop shell:
desktop surface, tray, dock, route params, context menu.
- Window/app manager:
app registry, app launch, focus, close, persisted open apps.
- CLI gameplay:
command execution, typewriter output, user/level progression.
- Persistence:
settings/user/tasks/patches through storage strategy.
- Media/audio:
icon/media helpers, sound playback, music and effects.
- Overlay and notifications:
global overlays and in-app notification stream.

## High-Level Diagram

```mermaid
graph TD
A[AppComponent] --> B[Router]
B --> C[DesktopComponent]
B --> D[Login and Boot Screens]

C --> E[ApplicationManagerService]
E --> F[AppWindowComponent Dynamic Apps]
E --> G[Dock and SystemTray]

C --> H[LevelLoaderComponent]
H --> I[GameConfigService]

F --> J[CliGameComponent]
J --> K[CLIService]
K --> I
K --> L[UserService]

J --> M[TypewriterService]
M --> N[SoundService]

L --> O[SettingsService]
O --> P[StorageService]

C --> Q[OverlayService]
C --> R[NotificationService]
R --> S[NotificationServerComponent]

G --> T[FileSystemService]
```

## Design Notes

- This codebase favors behavior in services over local component state.
- The primary maintainability pressure points are large services with mixed responsibilities and untyped dynamic data flows.
- Behavior stability depends heavily on preserving service public APIs while tightening internals.

59 changes: 59 additions & 0 deletions docs/ARCHITECTURE/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Security Notes

## Threat Model (Relevant to This App)

- XSS via dynamic HTML rendering (`innerHTML`, dynamic tooltip/notification content, terminal output).
- Open redirect or unsafe external navigation from route-driven URL values.
- Client-side secret exposure (API keys in browser bundle/environment files).
- Unsafe parsing and persistence of local storage data.
- Supply-chain risk from dependency drift and failing quality gates.

## Audit Findings

## 1) XSS Surface

Current sinks include:

- CLI output rendering with `[innerHTML]`
- notification message rendering with `[innerHTML]`
- tooltip text rendering with `[innerHTML]`
- raw `innerHTML` writes in settings subpanel fallback
- SVG trust bypass via `bypassSecurityTrustHtml`

Risk:
user-controlled or remotely controlled strings could execute markup/script payloads if not constrained.

## 2) External URL Handling

- Redirect guard opens decoded route param in a new tab with no allowlist.

Risk:
malicious URLs or script schemes can be triggered by crafted routes.

## 3) Secrets in Client

- OpenAI and weather API keys are configured for client-side use.

Risk:
keys are recoverable from browser context and can be abused.

## 4) Storage Trust

- App/session state read from local storage without robust schema validation.

Risk:
tampered storage payloads can produce runtime errors or unintended behavior.

## Recommended Mitigations (Planned)

1. Replace unsafe HTML sinks with safe rendering primitives and explicit formatting tokens.
2. Validate external URLs with strict scheme/domain checks before `window.open`.
3. Move third-party API calls requiring secrets behind a backend proxy/function.
4. Add schema guards for storage rehydration and fail-safe defaults.
5. Reduce `bypassSecurityTrust*` usage to controlled, immutable asset paths only.

## Operational Notes

- Firebase database rules are present; keep auth checks strict and avoid widening `.read` scopes.
- Use supported Node LTS for reproducible builds and security patch coverage.

Loading
Loading