@zigrivers/scaffold 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +87 -12
  2. package/content/knowledge/backend/backend-api-design.md +103 -0
  3. package/content/knowledge/backend/backend-architecture.md +100 -0
  4. package/content/knowledge/backend/backend-async-patterns.md +101 -0
  5. package/content/knowledge/backend/backend-auth-patterns.md +100 -0
  6. package/content/knowledge/backend/backend-conventions.md +105 -0
  7. package/content/knowledge/backend/backend-data-modeling.md +102 -0
  8. package/content/knowledge/backend/backend-deployment.md +100 -0
  9. package/content/knowledge/backend/backend-dev-environment.md +102 -0
  10. package/content/knowledge/backend/backend-observability.md +102 -0
  11. package/content/knowledge/backend/backend-project-structure.md +100 -0
  12. package/content/knowledge/backend/backend-requirements.md +103 -0
  13. package/content/knowledge/backend/backend-security.md +104 -0
  14. package/content/knowledge/backend/backend-testing.md +101 -0
  15. package/content/knowledge/backend/backend-worker-patterns.md +100 -0
  16. package/content/knowledge/cli/cli-architecture.md +101 -0
  17. package/content/knowledge/cli/cli-conventions.md +117 -0
  18. package/content/knowledge/cli/cli-dev-environment.md +121 -0
  19. package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
  20. package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
  21. package/content/knowledge/cli/cli-output-patterns.md +107 -0
  22. package/content/knowledge/cli/cli-project-structure.md +124 -0
  23. package/content/knowledge/cli/cli-requirements.md +101 -0
  24. package/content/knowledge/cli/cli-shell-integration.md +130 -0
  25. package/content/knowledge/cli/cli-testing.md +134 -0
  26. package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
  27. package/content/knowledge/web-app/web-app-architecture.md +116 -0
  28. package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
  29. package/content/knowledge/web-app/web-app-conventions.md +121 -0
  30. package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
  31. package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
  32. package/content/knowledge/web-app/web-app-deployment.md +134 -0
  33. package/content/knowledge/web-app/web-app-design-system.md +158 -0
  34. package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
  35. package/content/knowledge/web-app/web-app-observability.md +221 -0
  36. package/content/knowledge/web-app/web-app-project-structure.md +160 -0
  37. package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
  38. package/content/knowledge/web-app/web-app-requirements.md +112 -0
  39. package/content/knowledge/web-app/web-app-security.md +193 -0
  40. package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
  41. package/content/knowledge/web-app/web-app-testing.md +249 -0
  42. package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
  43. package/content/methodology/backend-overlay.yml +73 -0
  44. package/content/methodology/cli-overlay.yml +69 -0
  45. package/content/methodology/web-app-overlay.yml +79 -0
  46. package/dist/cli/commands/init.d.ts +12 -0
  47. package/dist/cli/commands/init.d.ts.map +1 -1
  48. package/dist/cli/commands/init.js +182 -13
  49. package/dist/cli/commands/init.js.map +1 -1
  50. package/dist/cli/commands/init.test.js +136 -0
  51. package/dist/cli/commands/init.test.js.map +1 -1
  52. package/dist/config/schema.d.ts +800 -32
  53. package/dist/config/schema.d.ts.map +1 -1
  54. package/dist/config/schema.js +48 -5
  55. package/dist/config/schema.js.map +1 -1
  56. package/dist/config/schema.test.js +156 -1
  57. package/dist/config/schema.test.js.map +1 -1
  58. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  59. package/dist/core/assembly/overlay-loader.js +2 -1
  60. package/dist/core/assembly/overlay-loader.js.map +1 -1
  61. package/dist/core/assembly/overlay-loader.test.js +34 -0
  62. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  63. package/dist/e2e/game-pipeline.test.js +1 -0
  64. package/dist/e2e/game-pipeline.test.js.map +1 -1
  65. package/dist/e2e/project-type-overlays.test.d.ts +15 -0
  66. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
  67. package/dist/e2e/project-type-overlays.test.js +534 -0
  68. package/dist/e2e/project-type-overlays.test.js.map +1 -0
  69. package/dist/types/config.d.ts +13 -2
  70. package/dist/types/config.d.ts.map +1 -1
  71. package/dist/types/index.d.ts +0 -1
  72. package/dist/types/index.d.ts.map +1 -1
  73. package/dist/types/index.js +0 -1
  74. package/dist/types/index.js.map +1 -1
  75. package/dist/wizard/questions.d.ts +16 -1
  76. package/dist/wizard/questions.d.ts.map +1 -1
  77. package/dist/wizard/questions.js +87 -3
  78. package/dist/wizard/questions.js.map +1 -1
  79. package/dist/wizard/questions.test.js +117 -4
  80. package/dist/wizard/questions.test.js.map +1 -1
  81. package/dist/wizard/wizard.d.ts +12 -0
  82. package/dist/wizard/wizard.d.ts.map +1 -1
  83. package/dist/wizard/wizard.js +16 -1
  84. package/dist/wizard/wizard.js.map +1 -1
  85. package/package.json +1 -1
  86. package/dist/types/wizard.d.ts +0 -14
  87. package/dist/types/wizard.d.ts.map +0 -1
  88. package/dist/types/wizard.js +0 -2
  89. package/dist/types/wizard.js.map +0 -1
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: web-app-security
3
+ description: XSS prevention, Content Security Policy, CSRF tokens, clickjacking, Subresource Integrity, dependency auditing, and OWASP top 10 for web apps
4
+ topics: [web-app, security, xss, csp, csrf, clickjacking, owasp, dependency-auditing]
5
+ ---
6
+
7
+ Web application security failures are among the most costly and common causes of data breaches. The OWASP Top 10 has catalogued the same categories of vulnerabilities for decades — not because they are new, but because they recur in every generation of frameworks and technologies. Understanding the attack vectors and their mitigations is not optional for engineers building user-facing web applications. Security must be designed in, not bolted on.
8
+
9
+ ## Summary
10
+
11
+ ### XSS (Cross-Site Scripting) Prevention
12
+
13
+ XSS occurs when attacker-controlled content is rendered as executable script in a victim's browser. It is the most prevalent web vulnerability class.
14
+
15
+ **Three XSS types:**
16
+ - **Stored XSS** — malicious script is stored in the database and rendered for all users (e.g., a comment containing `<script>`)
17
+ - **Reflected XSS** — malicious script is in the URL and reflected back in the response (e.g., a search page rendering the query parameter unsanitized)
18
+ - **DOM-based XSS** — malicious script is injected via client-side JavaScript manipulation of the DOM (e.g., `element.innerHTML = location.hash`)
19
+
20
+ **Primary defense: output encoding.** Modern React, Vue, and Angular frameworks automatically escape string interpolation. The vulnerabilities arise when developers bypass the framework: `dangerouslySetInnerHTML`, `v-html`, `innerHTML`, `document.write`, `eval()`.
21
+
22
+ **Content Security Policy (CSP):** A `Content-Security-Policy` response header instructs the browser to only execute scripts from trusted sources:
23
+
24
+ ```
25
+ Content-Security-Policy:
26
+ default-src 'self';
27
+ script-src 'self' https://trusted-cdn.com;
28
+ style-src 'self' 'unsafe-inline';
29
+ img-src 'self' data: https:;
30
+ font-src 'self' https://fonts.gstatic.com;
31
+ connect-src 'self' https://api.example.com;
32
+ frame-ancestors 'none';
33
+ upgrade-insecure-requests;
34
+ ```
35
+
36
+ `'unsafe-inline'` in `script-src` defeats the XSS protection benefit. Use nonces (`'nonce-{random}'`) or hashes instead for inline scripts.
37
+
38
+ ### Clickjacking Prevention
39
+
40
+ Clickjacking loads your application in a hidden `<iframe>` on an attacker's page. Users believe they are clicking on the attacker's UI but are actually clicking your application's buttons.
41
+
42
+ **Defense:** `Content-Security-Policy: frame-ancestors 'none'` (preferred) or the older `X-Frame-Options: DENY`. `frame-ancestors 'none'` prevents your app from being embedded in any frame. Use `frame-ancestors 'self'` if you need to allow same-origin framing.
43
+
44
+ ### CSRF Protection
45
+
46
+ Cross-Site Request Forgery tricks an authenticated user into submitting a request to your application from an attacker's site. The browser automatically sends cookies, making the forged request appear legitimate.
47
+
48
+ **Primary defense:** `SameSite=Strict` or `SameSite=Lax` cookies. With `SameSite=Strict`, the cookie is never sent on cross-site requests, making CSRF impossible.
49
+
50
+ **Secondary defense for legacy or cross-site scenarios:** Synchronizer token pattern — include a CSRF token in every form/request and validate it on the server.
51
+
52
+ ### Subresource Integrity (SRI)
53
+
54
+ When loading scripts or stylesheets from CDNs, SRI prevents a compromised CDN from serving malicious content:
55
+
56
+ ```html
57
+ <script
58
+ src="https://cdn.example.com/library.min.js"
59
+ integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux8P/C5b2E8x2U6sQ=="
60
+ crossorigin="anonymous"
61
+ ></script>
62
+ ```
63
+
64
+ The browser verifies the hash of the loaded resource. If it does not match, the resource is blocked. Generate hashes with: `openssl dgst -sha384 -binary file.js | openssl base64 -A`.
65
+
66
+ ### Dependency Auditing
67
+
68
+ Third-party dependencies are the most underestimated attack surface. The 2021 Log4Shell vulnerability and 2022 node-ipc supply chain attack demonstrated that a dependency in a dependency can cause a critical vulnerability.
69
+
70
+ **Baseline practices:**
71
+ - Run `npm audit` or `pnpm audit` in CI — fail the build on critical/high severities
72
+ - Pin exact dependency versions in production builds (lockfile required)
73
+ - Review dependency additions as carefully as code additions
74
+ - Subscribe to security advisories for critical dependencies (GitHub Dependabot, Snyk)
75
+
76
+ ## Deep Guidance
77
+
78
+ ### Content Security Policy Implementation
79
+
80
+ Deploying CSP in production requires a staged approach — a strict policy will break things:
81
+
82
+ **Phase 1: Report-only mode.** Use `Content-Security-Policy-Report-Only` with a `report-uri` endpoint. This logs violations without blocking anything. Collect violations for 1–2 weeks.
83
+
84
+ **Phase 2: Fix violations.** Address inline scripts (move to external files or use nonces), fix disallowed origins, and handle third-party widget requirements.
85
+
86
+ **Phase 3: Enforce.** Switch to `Content-Security-Policy`. Monitor violation reports for regressions.
87
+
88
+ ```typescript
89
+ // Next.js CSP with nonces (recommended over 'unsafe-inline')
90
+ import { headers } from 'next/headers';
91
+ import crypto from 'crypto';
92
+
93
+ export default function RootLayout({ children }) {
94
+ const nonce = crypto.randomBytes(16).toString('base64');
95
+
96
+ const cspHeader = [
97
+ `default-src 'self'`,
98
+ `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
99
+ `style-src 'self' 'nonce-${nonce}'`,
100
+ `img-src 'self' blob: data: https:`,
101
+ `font-src 'self'`,
102
+ `object-src 'none'`,
103
+ `base-uri 'self'`,
104
+ `form-action 'self'`,
105
+ `frame-ancestors 'none'`,
106
+ `upgrade-insecure-requests`,
107
+ ].join('; ');
108
+
109
+ return (
110
+ <html>
111
+ <head>
112
+ <meta httpEquiv="Content-Security-Policy" content={cspHeader} />
113
+ </head>
114
+ <body>{children}</body>
115
+ </html>
116
+ );
117
+ }
118
+ ```
119
+
120
+ ### Security Headers Checklist
121
+
122
+ Every production web app must serve these response headers:
123
+
124
+ ```
125
+ # Prevent clickjacking
126
+ Content-Security-Policy: frame-ancestors 'none'
127
+ X-Frame-Options: DENY # Legacy browsers
128
+
129
+ # Prevent MIME sniffing
130
+ X-Content-Type-Options: nosniff
131
+
132
+ # Enable HSTS (HTTP Strict Transport Security)
133
+ Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
134
+
135
+ # Control referrer information
136
+ Referrer-Policy: strict-origin-when-cross-origin
137
+
138
+ # Control browser features
139
+ Permissions-Policy: camera=(), microphone=(), geolocation=()
140
+
141
+ # Prevent IE compatibility mode
142
+ X-UA-Compatible: IE=edge
143
+ ```
144
+
145
+ Configure these in your CDN (Cloudflare, Vercel, Fastly) or reverse proxy rather than application code — they should apply to all responses including static assets.
146
+
147
+ ### HTML Sanitization for User Content
148
+
149
+ When you must render user-supplied HTML (rich text editors, markdown with HTML), use a server-side sanitization library:
150
+
151
+ ```typescript
152
+ import DOMPurify from 'isomorphic-dompurify';
153
+
154
+ // GOOD: Sanitize before storage and before rendering
155
+ function sanitizeUserHTML(html: string): string {
156
+ return DOMPurify.sanitize(html, {
157
+ ALLOWED_TAGS: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'br', 'blockquote'],
158
+ ALLOWED_ATTR: ['href', 'target', 'rel'],
159
+ FORBID_ATTR: ['style', 'class', 'id'],
160
+ ADD_ATTR: ['rel'], // Ensure all links have rel="noopener noreferrer"
161
+ FORCE_BODY: true,
162
+ });
163
+ }
164
+
165
+ // BAD: Rendering user HTML without sanitization
166
+ function UnsafeComponent({ userContent }) {
167
+ return <div dangerouslySetInnerHTML={{ __html: userContent }} />; // XSS risk
168
+ }
169
+
170
+ // GOOD: Sanitize before rendering
171
+ function SafeComponent({ userContent }) {
172
+ return <div dangerouslySetInnerHTML={{ __html: sanitizeUserHTML(userContent) }} />;
173
+ }
174
+ ```
175
+
176
+ **Always add `rel="noopener noreferrer"` to user-supplied links.** Without `noopener`, the linked page can access `window.opener` and redirect the original tab.
177
+
178
+ ### OWASP Top 10 Mapping
179
+
180
+ | OWASP 2021 | Web App Mitigation |
181
+ |---|---|
182
+ | A01 Broken Access Control | Server-side authz on every endpoint; never trust client-claimed identity |
183
+ | A02 Cryptographic Failures | TLS everywhere; never store passwords in plaintext; use bcrypt/Argon2 |
184
+ | A03 Injection | Parameterized queries; ORM; never concatenate user input into SQL/shell commands |
185
+ | A04 Insecure Design | Threat model before building auth/payment flows |
186
+ | A05 Security Misconfiguration | Headers checklist; disable debug endpoints in production; rotate default credentials |
187
+ | A06 Vulnerable Components | `npm audit` in CI; Dependabot alerts; pin lockfile |
188
+ | A07 Auth Failures | PKCE; rate limiting on login; MFA; session timeout |
189
+ | A08 Software Integrity | SRI for CDN assets; verify npm package checksums; signed commits |
190
+ | A09 Logging Failures | Log auth events; alert on anomalies; never log passwords or tokens |
191
+ | A10 SSRF | Validate and restrict URLs in server-side fetch calls; block private IP ranges |
192
+
193
+ Run a security review against this checklist before the first production deployment and after any major auth or data flow change.
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: web-app-session-patterns
3
+ description: Session management architecture, JWT vs cookie sessions, refresh token rotation, session storage, and hijacking prevention
4
+ topics: [web-app, auth, sessions, jwt, cookies, security, redis]
5
+ ---
6
+
7
+ Session management is the mechanism by which a web application recognizes a returning user between HTTP requests. Because HTTP is stateless, sessions are an application-level construct — and the design decisions here directly affect security, scalability, and user experience. The wrong session architecture causes token theft, session fixation attacks, memory exhaustion on the server, and logout failures that leave users permanently authenticated even after they believe they've signed out.
8
+
9
+ ## Summary
10
+
11
+ ### JWT vs Cookie Sessions
12
+
13
+ The two primary session patterns have fundamentally different security models:
14
+
15
+ **Cookie-based sessions (server-authoritative):**
16
+ - Server stores session state (database or Redis); client holds an opaque session ID in a cookie
17
+ - Immediate revocation: delete the session record and the user is logged out
18
+ - Scales horizontally when session storage is centralized (Redis cluster)
19
+ - HttpOnly + Secure + SameSite=Strict cookies resist XSS and CSRF simultaneously
20
+ - Each request requires a session store lookup — adds latency (~1–5 ms for Redis)
21
+
22
+ **JWT (stateless):**
23
+ - Server stores no session state; the signed token is the complete session
24
+ - No revocation without a token denylist (which re-introduces statefulness)
25
+ - Zero session store lookups per request — appropriate for stateless microservices
26
+ - Tokens can be stolen and reused until expiry — short expiry (15 minutes) is essential
27
+ - Refresh tokens enable long sessions without long-lived access tokens
28
+
29
+ **Rule of thumb:** Use cookie-based sessions for user-facing web apps where revocation matters. Use JWTs for service-to-service auth or APIs where clients are trusted and revocation is not required. Hybrid: short-lived JWTs + server-side refresh token rotation is the most common production pattern.
30
+
31
+ ### Refresh Token Rotation
32
+
33
+ Refresh token rotation is the critical security mechanism for long-lived JWT sessions:
34
+
35
+ 1. Access token expires in 15 minutes
36
+ 2. Client uses refresh token to obtain a new access token + new refresh token
37
+ 3. The old refresh token is immediately invalidated
38
+ 4. If the old refresh token is ever presented again, all sessions for that user are terminated (token reuse detection indicates theft)
39
+
40
+ This pattern detects token theft: if an attacker steals a refresh token and uses it, the legitimate user's next refresh attempt will fail and force re-authentication, alerting the user and invalidating the attacker's session.
41
+
42
+ ### Session Storage Backends
43
+
44
+ | Backend | Best For | Limits |
45
+ |---|---|---|
46
+ | Redis (single node) | Fast sessions, moderate scale | Single point of failure |
47
+ | Redis Cluster | High availability, large scale | Operational complexity |
48
+ | Redis Sentinel | Automatic failover for single-node Redis | Less scale than Cluster |
49
+ | Database (PostgreSQL) | Simpler stack, queryable sessions | Higher latency than Redis |
50
+ | In-process memory | Development only | Lost on restart, no horizontal scale |
51
+
52
+ For production, Redis is the standard choice: sub-millisecond lookups, TTL-based expiry is native, and session invalidation is an O(1) DEL command.
53
+
54
+ ### Token Expiry Strategy
55
+
56
+ Define expiry per token type based on risk tolerance:
57
+
58
+ - **Access token**: 15 minutes — short enough to limit exposure if stolen
59
+ - **Refresh token**: 7–30 days — rotate on each use; invalidate on logout
60
+ - **Remember-me token**: 90 days — separate from standard refresh, requires stronger audit
61
+ - **Email verification token**: 24–48 hours — single-use, invalidate on consumption
62
+ - **Password reset token**: 1 hour — single-use, invalidate on consumption, invalidate all sessions on use
63
+
64
+ ## Deep Guidance
65
+
66
+ ### Secure Cookie Configuration
67
+
68
+ Every session cookie must be configured with all three security attributes:
69
+
70
+ ```typescript
71
+ // Express.js session cookie configuration
72
+ app.use(session({
73
+ name: '__Host-sessionId', // __Host- prefix requires Secure + path=/ + no domain
74
+ secret: process.env.SESSION_SECRET,
75
+ resave: false,
76
+ saveUninitialized: false,
77
+ cookie: {
78
+ httpOnly: true, // Inaccessible to JavaScript — prevents XSS token theft
79
+ secure: true, // HTTPS only — prevents transmission over HTTP
80
+ sameSite: 'strict', // Not sent on cross-site requests — prevents CSRF
81
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
82
+ path: '/',
83
+ },
84
+ store: new RedisStore({ client: redisClient }),
85
+ }));
86
+ ```
87
+
88
+ The `__Host-` cookie name prefix is a browser-enforced security policy that prevents subdomain hijacking — it requires `Secure`, `path=/`, and no `Domain` attribute. Use it for session cookies in production.
89
+
90
+ ### Refresh Token Rotation Implementation
91
+
92
+ ```typescript
93
+ interface TokenPair {
94
+ accessToken: string;
95
+ refreshToken: string;
96
+ accessTokenExpiresAt: Date;
97
+ }
98
+
99
+ async function rotateRefreshToken(
100
+ incomingRefreshToken: string
101
+ ): Promise<TokenPair> {
102
+ // 1. Look up the incoming refresh token
103
+ const storedToken = await db.refreshToken.findUnique({
104
+ where: { token: incomingRefreshToken },
105
+ include: { user: true },
106
+ });
107
+
108
+ if (!storedToken) {
109
+ // Token not found — could be expired, already rotated, or forged
110
+ throw new AuthError('INVALID_REFRESH_TOKEN');
111
+ }
112
+
113
+ if (storedToken.usedAt !== null) {
114
+ // REUSE DETECTED: token was already rotated — potential theft
115
+ // Invalidate ALL refresh tokens for this user
116
+ await db.refreshToken.updateMany({
117
+ where: { userId: storedToken.userId },
118
+ data: { revokedAt: new Date() },
119
+ });
120
+ throw new AuthError('TOKEN_REUSE_DETECTED');
121
+ }
122
+
123
+ if (storedToken.revokedAt !== null || storedToken.expiresAt < new Date()) {
124
+ throw new AuthError('REFRESH_TOKEN_EXPIRED');
125
+ }
126
+
127
+ // 2. Mark old token as used (not deleted — needed for reuse detection)
128
+ await db.refreshToken.update({
129
+ where: { id: storedToken.id },
130
+ data: { usedAt: new Date() },
131
+ });
132
+
133
+ // 3. Issue new token pair
134
+ const newAccessToken = signAccessToken({ sub: storedToken.userId });
135
+ const newRefreshToken = await db.refreshToken.create({
136
+ data: {
137
+ userId: storedToken.userId,
138
+ token: generateSecureToken(),
139
+ expiresAt: addDays(new Date(), 30),
140
+ parentTokenId: storedToken.id, // Track rotation chain
141
+ },
142
+ });
143
+
144
+ return {
145
+ accessToken: newAccessToken,
146
+ refreshToken: newRefreshToken.token,
147
+ accessTokenExpiresAt: addMinutes(new Date(), 15),
148
+ };
149
+ }
150
+ ```
151
+
152
+ ### Session Hijacking Prevention
153
+
154
+ Beyond secure cookies, defend against session hijacking at the application layer:
155
+
156
+ **IP binding (optional, use carefully):**
157
+ - Store the client IP at session creation and validate on each request
158
+ - Breaks for legitimate users on mobile networks (IP changes per cell tower)
159
+ - Use only for high-security flows (admin panels, bank-grade apps), not general user sessions
160
+
161
+ **User-Agent fingerprint:**
162
+ - Store a hash of the User-Agent string at session creation
163
+ - Validate on each request; suspicious changes invalidate the session
164
+ - Not a strong defense (UA is spoofable) but raises the cost of attack
165
+
166
+ **Session fixation prevention:**
167
+ - Always regenerate the session ID on successful authentication
168
+ - Never allow the session ID to be set via URL parameters (cookie-only)
169
+ - In Express: call `req.session.regenerate()` after successful login
170
+
171
+ **Concurrent session limits:**
172
+ - Track active session count per user in Redis
173
+ - On new login, offer the user the choice to log out other sessions or enforce a maximum
174
+ - Essential for compliance in regulated industries
175
+
176
+ ### Redis Session Store with TTL
177
+
178
+ ```typescript
179
+ import Redis from 'ioredis';
180
+ import RedisStore from 'connect-redis';
181
+
182
+ const redisClient = new Redis({
183
+ host: process.env.REDIS_HOST,
184
+ port: 6379,
185
+ // Lazy connect — don't block startup if Redis is temporarily unavailable
186
+ lazyConnect: true,
187
+ // Retry strategy — exponential backoff
188
+ retryStrategy: (times: number) => Math.min(times * 50, 2000),
189
+ });
190
+
191
+ // Sessions expire automatically via Redis TTL — no cleanup job needed
192
+ const sessionStore = new RedisStore({
193
+ client: redisClient,
194
+ prefix: 'sess:',
195
+ ttl: 7 * 24 * 60 * 60, // 7 days in seconds
196
+ });
197
+
198
+ // Invalidate a specific session (logout)
199
+ async function invalidateSession(sessionId: string): Promise<void> {
200
+ await redisClient.del(`sess:${sessionId}`);
201
+ }
202
+
203
+ // Invalidate all sessions for a user (force logout everywhere)
204
+ async function invalidateAllUserSessions(userId: string): Promise<void> {
205
+ // Requires storing a user→sessions index in Redis
206
+ const sessionIds = await redisClient.smembers(`user:sessions:${userId}`);
207
+ if (sessionIds.length > 0) {
208
+ await redisClient.del(...sessionIds.map(id => `sess:${id}`));
209
+ await redisClient.del(`user:sessions:${userId}`);
210
+ }
211
+ }
212
+ ```
213
+
214
+ Maintain a `user:sessions:{userId}` Redis set to enable "logout everywhere" — essential for security incident response.
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: web-app-testing
3
+ description: Component testing with Testing Library, SSR testing, E2E testing with Playwright, visual regression, accessibility testing with axe-core, and Lighthouse CI
4
+ topics: [web-app, testing, playwright, testing-library, accessibility, visual-regression, lighthouse, e2e]
5
+ ---
6
+
7
+ Web application testing requires covering multiple distinct layers: component behavior, server rendering correctness, end-to-end user flows, visual appearance, accessibility compliance, and performance budgets. Each layer catches different classes of bugs and has different cost/value characteristics. The correct strategy invests heavily in component and integration tests (fast feedback, high coverage), uses E2E tests for critical user journeys (slow but comprehensive), and enforces visual and performance budgets in CI (catches regressions before users do).
8
+
9
+ ## Summary
10
+
11
+ ### Testing Layers
12
+
13
+ **Component tests (Testing Library):** Test components in isolation from the DOM's perspective — interactions, rendering, and state changes. These form the bulk of the test suite. Use `@testing-library/user-event` for realistic interactions, not `fireEvent`.
14
+
15
+ **Integration tests:** Test pages or feature slices with real data flow but mocked network calls. Verify that components interact correctly. In Next.js: test pages as React components with a mocked router.
16
+
17
+ **SSR tests (render-to-string):** Verify server-rendered HTML is correct and does not throw. Check that SSR and hydration produce consistent output (no hydration mismatches). Use `@testing-library/react`'s `renderToStaticMarkup` or framework-specific utilities.
18
+
19
+ **E2E tests (Playwright):** Test complete user flows in a real browser against a running application. Reserve for high-value, business-critical flows: registration, login, checkout, core product value proposition. Keep the E2E suite small and fast (target under 10 minutes for the critical path).
20
+
21
+ **Visual regression tests:** Screenshot comparison against committed baselines. Catch unintended UI changes from CSS refactors, component updates, or dependency upgrades. Run against a static Storybook deployment for fast, stable comparisons.
22
+
23
+ **Accessibility tests (axe-core):** Automated WCAG compliance checking. Catches ~30–40% of accessibility issues automatically. Not a substitute for manual testing with a screen reader, but essential for catching regressions.
24
+
25
+ **Performance tests (Lighthouse CI):** Enforce performance budgets in CI. Fail the build if LCP, CLS, or INP regressions are detected.
26
+
27
+ ### Testing Library Principles
28
+
29
+ Testing Library is designed around the principle that tests should resemble how users interact with the application:
30
+
31
+ - **Query by role first** — `getByRole('button', { name: 'Submit' })` over `getByTestId`
32
+ - **Query by label text** — `getByLabelText('Email address')` for form inputs
33
+ - **Never query by CSS class** — classes are implementation details, not behavior
34
+ - **Use `userEvent` not `fireEvent`** — `userEvent.type()` simulates real keystrokes including focus, blur, and change events; `fireEvent` dispatches synthetic events that skip browser behaviors
35
+
36
+ ### Playwright for E2E
37
+
38
+ Playwright supports Chromium, Firefox, and WebKit. Configure it to run against your staging environment in CI and your local dev server locally. Key features:
39
+ - Auto-waiting: Playwright automatically waits for elements to be ready before interacting
40
+ - Network mocking: intercept and mock API responses in tests
41
+ - Trace viewer: visual debugging of test failures with full network and DOM timeline
42
+ - Component testing: Playwright can also test components in isolation (alternative to Testing Library for teams that want one tool)
43
+
44
+ ## Deep Guidance
45
+
46
+ ### Component Testing Patterns
47
+
48
+ ```typescript
49
+ // UserProfile.test.tsx — Testing Library patterns
50
+ import { render, screen, waitFor } from '@testing-library/react';
51
+ import userEvent from '@testing-library/user-event';
52
+ import { UserProfile } from './UserProfile';
53
+ import { server } from '@/mocks/server'; // MSW mock server
54
+ import { http, HttpResponse } from 'msw';
55
+
56
+ describe('UserProfile', () => {
57
+ it('displays user data after loading', async () => {
58
+ render(<UserProfile userId="user-123" />);
59
+
60
+ // Loading state
61
+ expect(screen.getByRole('status')).toBeInTheDocument();
62
+
63
+ // Data loads
64
+ await screen.findByText('Jane Smith'); // Awaits element appearance
65
+ expect(screen.getByText('jane@example.com')).toBeInTheDocument();
66
+ expect(screen.queryByRole('status')).not.toBeInTheDocument();
67
+ });
68
+
69
+ it('allows editing the display name', async () => {
70
+ const user = userEvent.setup();
71
+ render(<UserProfile userId="user-123" />);
72
+
73
+ await screen.findByText('Jane Smith');
74
+ await user.click(screen.getByRole('button', { name: 'Edit profile' }));
75
+
76
+ const nameInput = screen.getByLabelText('Display name');
77
+ await user.clear(nameInput);
78
+ await user.type(nameInput, 'Jane Doe');
79
+ await user.click(screen.getByRole('button', { name: 'Save changes' }));
80
+
81
+ await screen.findByText('Profile updated successfully');
82
+ expect(screen.getByText('Jane Doe')).toBeInTheDocument();
83
+ });
84
+
85
+ it('shows an error message when the save fails', async () => {
86
+ // Override the default MSW handler for this test
87
+ server.use(
88
+ http.patch('/api/users/:id', () =>
89
+ HttpResponse.json({ error: 'Server error' }, { status: 500 })
90
+ )
91
+ );
92
+
93
+ const user = userEvent.setup();
94
+ render(<UserProfile userId="user-123" />);
95
+
96
+ await screen.findByText('Jane Smith');
97
+ await user.click(screen.getByRole('button', { name: 'Edit profile' }));
98
+ await user.click(screen.getByRole('button', { name: 'Save changes' }));
99
+
100
+ await screen.findByRole('alert');
101
+ expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
102
+ });
103
+ });
104
+ ```
105
+
106
+ Use Mock Service Worker (MSW) for network mocking — it intercepts requests at the network level, making tests work identically in browser and Node environments.
107
+
108
+ ### Playwright E2E Tests
109
+
110
+ ```typescript
111
+ // tests/e2e/auth.spec.ts — Playwright E2E for critical auth flow
112
+ import { test, expect } from '@playwright/test';
113
+
114
+ test.describe('Authentication', () => {
115
+ test('user can register and log in', async ({ page }) => {
116
+ const email = `test-${Date.now()}@example.com`;
117
+
118
+ // Registration
119
+ await page.goto('/register');
120
+ await page.getByLabel('Email address').fill(email);
121
+ await page.getByLabel('Password').fill('SecureP@ss123');
122
+ await page.getByLabel('Confirm password').fill('SecureP@ss123');
123
+ await page.getByRole('button', { name: 'Create account' }).click();
124
+
125
+ // Should redirect to onboarding
126
+ await expect(page).toHaveURL('/onboarding');
127
+ await expect(page.getByText('Welcome')).toBeVisible();
128
+
129
+ // Log out
130
+ await page.getByRole('button', { name: 'Account menu' }).click();
131
+ await page.getByRole('menuitem', { name: 'Sign out' }).click();
132
+ await expect(page).toHaveURL('/');
133
+
134
+ // Log in
135
+ await page.goto('/login');
136
+ await page.getByLabel('Email address').fill(email);
137
+ await page.getByLabel('Password').fill('SecureP@ss123');
138
+ await page.getByRole('button', { name: 'Sign in' }).click();
139
+
140
+ await expect(page).toHaveURL('/dashboard');
141
+ });
142
+
143
+ test('shows error for invalid credentials', async ({ page }) => {
144
+ await page.goto('/login');
145
+ await page.getByLabel('Email address').fill('nonexistent@example.com');
146
+ await page.getByLabel('Password').fill('wrongpassword');
147
+ await page.getByRole('button', { name: 'Sign in' }).click();
148
+
149
+ await expect(page.getByRole('alert')).toContainText('Invalid email or password');
150
+ await expect(page).toHaveURL('/login');
151
+ });
152
+ });
153
+ ```
154
+
155
+ ### Accessibility Testing with axe-core
156
+
157
+ ```typescript
158
+ // Integrate axe-core into component tests
159
+ import { render } from '@testing-library/react';
160
+ import { axe, toHaveNoViolations } from 'jest-axe';
161
+
162
+ expect.extend(toHaveNoViolations);
163
+
164
+ describe('Accessibility', () => {
165
+ it('LoginForm has no accessibility violations', async () => {
166
+ const { container } = render(<LoginForm />);
167
+ const results = await axe(container);
168
+ expect(results).toHaveNoViolations();
169
+ });
170
+ });
171
+
172
+ // Playwright accessibility snapshot
173
+ test('dashboard is accessible', async ({ page }) => {
174
+ await page.goto('/dashboard');
175
+ const accessibilityScanResults = await new AxeBuilder({ page })
176
+ .withTags(['wcag2a', 'wcag2aa'])
177
+ .analyze();
178
+ expect(accessibilityScanResults.violations).toEqual([]);
179
+ });
180
+ ```
181
+
182
+ Axe-core checks: missing alt text, insufficient color contrast, missing form labels, improper heading hierarchy, keyboard navigation issues, and ARIA attribute misuse. Run it against every page in CI.
183
+
184
+ ### Lighthouse CI Configuration
185
+
186
+ ```yaml
187
+ # lighthouserc.yml
188
+ ci:
189
+ collect:
190
+ url:
191
+ - 'http://localhost:3000/'
192
+ - 'http://localhost:3000/dashboard'
193
+ numberOfRuns: 3 # Average across multiple runs for stability
194
+ settings:
195
+ preset: 'desktop'
196
+ throttling:
197
+ rttMs: 40
198
+ throughputKbps: 10240
199
+ cpuSlowdownMultiplier: 1
200
+
201
+ assert:
202
+ preset: 'lighthouse:no-pwa'
203
+ assertions:
204
+ # Core Web Vitals
205
+ largest-contentful-paint:
206
+ - error
207
+ - maxNumericValue: 2500
208
+ aggregationMethod: optimistic # Use best of N runs
209
+ cumulative-layout-shift:
210
+ - error
211
+ - maxNumericValue: 0.1
212
+ total-blocking-time:
213
+ - warn
214
+ - maxNumericValue: 300
215
+
216
+ # Performance budget
217
+ uses-optimized-images: ['warn', { minScore: 0.9 }]
218
+ uses-text-compression: ['error', { minScore: 1 }]
219
+ render-blocking-resources: ['warn', { minScore: 0.8 }]
220
+
221
+ # Accessibility
222
+ categories:accessibility: ['error', { minScore: 0.9 }]
223
+ ```
224
+
225
+ Run Lighthouse CI in a separate CI step after deployment to your staging environment. Fail only on errors (hard regressions); warn on improvements.
226
+
227
+ ### SSR Hydration Testing
228
+
229
+ ```typescript
230
+ // Verify SSR output and hydration consistency
231
+ import { renderToString } from 'react-dom/server';
232
+ import { render } from '@testing-library/react';
233
+
234
+ test('ProductCard renders consistently in SSR and client', () => {
235
+ const props = { name: 'Widget', price: 29.99 };
236
+
237
+ // SSR render
238
+ const ssrHTML = renderToString(<ProductCard {...props} />);
239
+
240
+ // Client render
241
+ const { container } = render(<ProductCard {...props} />);
242
+
243
+ // Compare — normalize whitespace differences
244
+ const normalize = (html: string) => html.replace(/\s+/g, ' ').trim();
245
+ expect(normalize(container.innerHTML)).toBe(normalize(ssrHTML));
246
+ });
247
+ ```
248
+
249
+ Hydration mismatches produce React warnings in development and can cause visual flicker in production. Test SSR/client consistency for any component that uses `Date.now()`, `Math.random()`, browser APIs, or dynamic imports.