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
84 changes: 84 additions & 0 deletions examples/angular-monitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Observe Angular SSR traffic with WebDecoy

A runnable Angular 22 SSR application using published `@webdecoy/express` and
`@webdecoy/node` 0.18.0. This is WebDecoy's request middleware, not FCaptcha.

## Run the production server locally

Use a current Node release supported by Angular 22 (tested with Node 26.5).
The nested app intentionally installs published packages independently of the
SDK monorepo's workspaces:

```sh
cd examples/angular-monitor/app
npm ci --workspaces=false
npm run build
npm test
npm run serve
```

Open http://127.0.0.1:4300 and click **Load public catalog**. The server logs a
`webdecoy-decision` record. Start with no API key for an entirely local demo.
Do not use `ng serve` to verify production middleware placement: build and run
the emitted Node server as above.

## Prove the pipeline

```sh
curl -i http://127.0.0.1:4300/api/products
curl -i http://127.0.0.1:4300/.env
curl -i -A 'WebDecoy-Test/1.0' http://127.0.0.1:4300/api/products
```

- Catalog: 200 with two public demo items.
- Tripwire: terminal says `DENY`, `tripwire: true`, `wouldBlock: true`; HTTP stays
404 because monitor mode preserves the application's response. There is no
real `.env` file at this route.
- Reserved test: terminal labels `testTrigger: true`. Without a key,
`dashboardConfigured: false` and `reportingError: true` explicitly show that
nothing was reported. The catalog still returns 200 in monitor mode.

The per-process rate rule is 60 requests per 60 seconds. Monitor mode records
its denials without returning 429. It includes page/API requests, not actual
static assets or the health endpoint. This low limit is for learning, not a
production recommendation.

## Optional dashboard connection

Set `WEBDECOY_API_KEY` in your **server environment**, using your own key from
https://app.webdecoy.com. Restart, then send the reserved test request again.
Check the dashboard for the labelled test event and check for reporting errors.
A configured key alone does not prove delivery. Never put the key in Angular
configuration, a component, an HTTP interceptor, or a browser bundle. The SDK
can send request metadata when a key is supplied; use your own test traffic.
No cloud credentials or dashboard delivery are included in automated tests.

## Architecture and scope

`src/server.ts` mounts WebDecoy before both the API and AngularNodeAppEngine.
Only real static files and `/health` bypass the middleware. Server routes use
`RenderMode.Server`. If a CDN serves prerendered HTML without reaching Node,
this middleware cannot observe that request. Protect a separately hosted API
in its own backend as well.

The Angular component uses standalone APIs, `inject(HttpClient)`, signals and
`@for`. Its browser code contains no WebDecoy key or security decision logic.
The sample uses no browser globals or HTML honeytoken injection, so it avoids
adding DOM mutations during hydration. `honeytoken: false` is explicit.

The demo logs limited structured decisions, not IPs, query strings, headers,
tokens or bodies. These are rule outcomes, not proof of a visitor's identity or
intent. Monitor mode never means a request was blocked. SDK errors fail open
by default; authentication and authorization remain the application's job.

## Deployment considerations

The sample binds to loopback and trusts no forwarding headers. Configure trusted
proxies for your actual deployment; don't blindly trust arbitrary X-Forwarded-For.
Keep in-process rate limits to one process, or use a shared store across replicas.
Observe representative traffic before deciding whether to switch to enforcement.
If you do, make the mode change explicit and test legitimate clients too.

Tests exercise the built production server: SSR HTML, catalog availability,
tripwire receipts, the reserved trigger and rate-limit observation. They do not
measure detection accuracy or verify cloud reporting.
17 changes: 17 additions & 0 deletions examples/angular-monitor/app/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false

[*.md]
max_line_length = off
trim_trailing_whitespace = false
44 changes: 44 additions & 0 deletions examples/angular-monitor/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/mcp.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/

# System files
.DS_Store
Thumbs.db
12 changes: 12 additions & 0 deletions examples/angular-monitor/app/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}
1 change: 1 addition & 0 deletions examples/angular-monitor/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See [the tutorial README](../README.md) for setup, probes and test scope.
81 changes: 81 additions & 0 deletions examples/angular-monitor/app/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "npm"
},
"newProjectRoot": "projects",
"projects": {
"webdecoy-angular": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.css"
],
"server": "src/main.server.ts",
"outputMode": "server",
"security": {
"allowedHosts": [
"127.0.0.1",
"localhost"
]
},
"ssr": {
"entry": "src/server.ts"
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "webdecoy-angular:build:production"
},
"development": {
"buildTarget": "webdecoy-angular:build:development"
}
},
"defaultConfiguration": "development"
}
}
}
}
}
Loading
Loading