@tekyzinc/gsd-t 2.50.11 → 2.50.12
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.
- package/CHANGELOG.md +11 -3
- package/commands/gsd-t-debug.md +6 -0
- package/commands/gsd-t-execute.md +6 -0
- package/commands/gsd-t-help.md +1 -1
- package/commands/gsd-t-quick.md +6 -0
- package/docs/GSD-T-README.md +32 -10
- package/package.json +1 -1
- package/templates/stacks/_auth.md +324 -0
- package/templates/stacks/fastapi.md +377 -0
- package/templates/stacks/llm.md +541 -0
- package/templates/stacks/prisma.md +400 -0
- package/templates/stacks/queues.md +437 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
-
## [2.50.
|
|
5
|
+
## [2.50.12] - 2026-03-25
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
|
-
- **
|
|
8
|
+
- **23 new stack rule files** — python, flutter, tailwind, react-native, vite, nextjs, vue, docker, postgresql (with graph-in-SQL section), github-actions, rest-api, supabase, firebase, graphql, zustand, redux, neo4j, playwright, fastapi, llm (with RAG patterns section), prisma, queues, _auth (universal). Total: 27 stack rules (was 4).
|
|
9
|
+
- **`_auth.md`** (universal) — email-first registration, auth provider abstraction (Cognito/Firebase/Google), token management, password policy, session management, social auth/OAuth, email verification, MFA, authorization/RBAC, auth security, auth UI patterns.
|
|
10
|
+
- **`fastapi.md`** — dependency injection, Pydantic request/response models, lifespan events, BackgroundTasks, async patterns, auto-generated OpenAPI docs.
|
|
11
|
+
- **`llm.md`** — provider-agnostic LLM patterns: structured outputs, streaming, error/retry, token management, conversation state, tool/function calling, RAG patterns (chunking, embeddings, retrieval), prompt management, testing, cost/observability.
|
|
12
|
+
- **`prisma.md`** — schema modeling, migrations, typed client usage, relation queries, transactions, seeding, N+1 prevention.
|
|
13
|
+
- **`queues.md`** — BullMQ/Bull, SQS, RabbitMQ, Celery patterns: idempotent handlers, dead letter queues, retry/backoff, job deduplication, graceful shutdown.
|
|
9
14
|
- **Playwright best practices** — coverage matrix per feature, pairwise combinatorial testing, state transition testing, multi-step workflow testing, Page Object Model, API mocking patterns. Enforces rigorous test depth across permutations.
|
|
10
15
|
- **react.md expanded** — added state management decision table, form management (react-hook-form + zod), React naming conventions (3 new sections from external best practices review).
|
|
16
|
+
- **Project-level stack overrides** — `.gsd-t/stacks/` directory for per-project customization of global stack rules. Local files replace global files of the same name.
|
|
11
17
|
|
|
12
18
|
### Changed
|
|
13
|
-
- Stack detection in execute, quick, and debug commands updated to cover all
|
|
19
|
+
- Stack detection in execute, quick, and debug commands updated to cover all 27 stack files with conditional detection per project dependencies.
|
|
20
|
+
- Detection refactored from one-liner to structured bash with `_sf()` (local override resolver) and `_add()` helper functions.
|
|
14
21
|
- PostgreSQL graph-in-SQL patterns (adjacency lists, junction tables, recursive CTEs) added to postgresql.md based on real project analysis.
|
|
22
|
+
- GSD-T-README.md stack detection table expanded to list all 27 files with their detection triggers.
|
|
15
23
|
|
|
16
24
|
## [2.46.11] - 2026-03-24
|
|
17
25
|
|
package/commands/gsd-t-debug.md
CHANGED
|
@@ -37,10 +37,16 @@ if [ -d "$STACKS_DIR" ]; then
|
|
|
37
37
|
grep -q '"@reduxjs/toolkit"' package.json 2>/dev/null && _add redux.md
|
|
38
38
|
grep -q '"neo4j-driver"' package.json 2>/dev/null && _add neo4j.md
|
|
39
39
|
grep -qE '"(pg|prisma|drizzle-orm|knex)"' package.json 2>/dev/null && _add postgresql.md
|
|
40
|
+
grep -qE '"(prisma|@prisma/client)"' package.json 2>/dev/null && _add prisma.md
|
|
41
|
+
grep -qE '"(bullmq|bull|amqplib|@aws-sdk/client-sqs|bee-queue|agenda)"' package.json 2>/dev/null && _add queues.md
|
|
42
|
+
grep -qE '"(openai|anthropic|@anthropic-ai/sdk|langchain|llama-index|@google/generative-ai)"' package.json 2>/dev/null && _add llm.md
|
|
40
43
|
fi
|
|
41
44
|
([ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "Pipfile" ]) && _add python.md
|
|
42
45
|
([ -f "requirements.txt" ] && grep -q "psycopg" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "psycopg" pyproject.toml 2>/dev/null) && _add postgresql.md
|
|
43
46
|
([ -f "requirements.txt" ] && grep -q "neo4j" requirements.txt 2>/dev/null) && _add neo4j.md
|
|
47
|
+
([ -f "requirements.txt" ] && grep -q "fastapi" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "fastapi" pyproject.toml 2>/dev/null) && _add fastapi.md
|
|
48
|
+
([ -f "requirements.txt" ] && grep -qE "(celery|dramatiq|rq|arq)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(celery|dramatiq|rq|arq)" pyproject.toml 2>/dev/null) && _add queues.md
|
|
49
|
+
([ -f "requirements.txt" ] && grep -qE "(openai|anthropic|langchain|llama.index)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(openai|anthropic|langchain|llama.index)" pyproject.toml 2>/dev/null) && _add llm.md
|
|
44
50
|
[ -f "pubspec.yaml" ] && _add flutter.md
|
|
45
51
|
[ -f "Dockerfile" ] && _add docker.md
|
|
46
52
|
[ -d ".github/workflows" ] && _add github-actions.md
|
|
@@ -156,12 +156,18 @@ if [ -d "$STACKS_DIR" ]; then
|
|
|
156
156
|
grep -q '"@reduxjs/toolkit"' package.json 2>/dev/null && _add redux.md
|
|
157
157
|
grep -q '"neo4j-driver"' package.json 2>/dev/null && _add neo4j.md
|
|
158
158
|
grep -qE '"(pg|prisma|drizzle-orm|knex)"' package.json 2>/dev/null && _add postgresql.md
|
|
159
|
+
grep -qE '"(prisma|@prisma/client)"' package.json 2>/dev/null && _add prisma.md
|
|
160
|
+
grep -qE '"(bullmq|bull|amqplib|@aws-sdk/client-sqs|bee-queue|agenda)"' package.json 2>/dev/null && _add queues.md
|
|
161
|
+
grep -qE '"(openai|anthropic|@anthropic-ai/sdk|langchain|llama-index|@google/generative-ai)"' package.json 2>/dev/null && _add llm.md
|
|
159
162
|
fi
|
|
160
163
|
|
|
161
164
|
# File-based detection (no package.json needed)
|
|
162
165
|
([ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "Pipfile" ]) && _add python.md
|
|
163
166
|
([ -f "requirements.txt" ] && grep -q "psycopg" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "psycopg" pyproject.toml 2>/dev/null) && _add postgresql.md
|
|
164
167
|
([ -f "requirements.txt" ] && grep -q "neo4j" requirements.txt 2>/dev/null) && _add neo4j.md
|
|
168
|
+
([ -f "requirements.txt" ] && grep -q "fastapi" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "fastapi" pyproject.toml 2>/dev/null) && _add fastapi.md
|
|
169
|
+
([ -f "requirements.txt" ] && grep -qE "(celery|dramatiq|rq|arq)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(celery|dramatiq|rq|arq)" pyproject.toml 2>/dev/null) && _add queues.md
|
|
170
|
+
([ -f "requirements.txt" ] && grep -qE "(openai|anthropic|langchain|llama.index)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(openai|anthropic|langchain|llama.index)" pyproject.toml 2>/dev/null) && _add llm.md
|
|
165
171
|
[ -f "pubspec.yaml" ] && _add flutter.md
|
|
166
172
|
[ -f "Dockerfile" ] && _add docker.md
|
|
167
173
|
[ -d ".github/workflows" ] && _add github-actions.md
|
package/commands/gsd-t-help.md
CHANGED
|
@@ -258,7 +258,7 @@ Use these when user asks for help on a specific command:
|
|
|
258
258
|
- **Use when**: Ready to implement
|
|
259
259
|
- **Note (M22)**: Task-level fresh dispatch (one subagent per task, ~10-20% context each). Team mode uses worktree isolation (`isolation: "worktree"`) — zero file conflicts. Adaptive replanning between domain completions.
|
|
260
260
|
- **Note (M26)**: Active rule injection — evaluates declarative rules from rules.jsonl before dispatching each domain's tasks. Fires matching rules as warnings in subagent prompts.
|
|
261
|
-
- **Note (M29)**: Stack Rules Engine — auto-detects project tech stack from manifest files and injects mandatory best-practice rules into each task subagent prompt. Universal rules (`_security.md`) always apply; stack-specific rules layer on top. Violations are task failures (same weight as contract violations).
|
|
261
|
+
- **Note (M29)**: Stack Rules Engine — auto-detects project tech stack from manifest files and injects mandatory best-practice rules into each task subagent prompt. Universal rules (`_security.md`, `_auth.md`) always apply; stack-specific rules layer on top. Violations are task failures (same weight as contract violations).
|
|
262
262
|
|
|
263
263
|
### test-sync
|
|
264
264
|
- **Summary**: Keep tests aligned with code changes
|
package/commands/gsd-t-quick.md
CHANGED
|
@@ -41,10 +41,16 @@ if [ -d "$STACKS_DIR" ]; then
|
|
|
41
41
|
grep -q '"@reduxjs/toolkit"' package.json 2>/dev/null && _add redux.md
|
|
42
42
|
grep -q '"neo4j-driver"' package.json 2>/dev/null && _add neo4j.md
|
|
43
43
|
grep -qE '"(pg|prisma|drizzle-orm|knex)"' package.json 2>/dev/null && _add postgresql.md
|
|
44
|
+
grep -qE '"(prisma|@prisma/client)"' package.json 2>/dev/null && _add prisma.md
|
|
45
|
+
grep -qE '"(bullmq|bull|amqplib|@aws-sdk/client-sqs|bee-queue|agenda)"' package.json 2>/dev/null && _add queues.md
|
|
46
|
+
grep -qE '"(openai|anthropic|@anthropic-ai/sdk|langchain|llama-index|@google/generative-ai)"' package.json 2>/dev/null && _add llm.md
|
|
44
47
|
fi
|
|
45
48
|
([ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "Pipfile" ]) && _add python.md
|
|
46
49
|
([ -f "requirements.txt" ] && grep -q "psycopg" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "psycopg" pyproject.toml 2>/dev/null) && _add postgresql.md
|
|
47
50
|
([ -f "requirements.txt" ] && grep -q "neo4j" requirements.txt 2>/dev/null) && _add neo4j.md
|
|
51
|
+
([ -f "requirements.txt" ] && grep -q "fastapi" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -q "fastapi" pyproject.toml 2>/dev/null) && _add fastapi.md
|
|
52
|
+
([ -f "requirements.txt" ] && grep -qE "(celery|dramatiq|rq|arq)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(celery|dramatiq|rq|arq)" pyproject.toml 2>/dev/null) && _add queues.md
|
|
53
|
+
([ -f "requirements.txt" ] && grep -qE "(openai|anthropic|langchain|llama.index)" requirements.txt 2>/dev/null || [ -f "pyproject.toml" ] && grep -qE "(openai|anthropic|langchain|llama.index)" pyproject.toml 2>/dev/null) && _add llm.md
|
|
48
54
|
[ -f "pubspec.yaml" ] && _add flutter.md
|
|
49
55
|
[ -f "Dockerfile" ] && _add docker.md
|
|
50
56
|
[ -d ".github/workflows" ] && _add github-actions.md
|
package/docs/GSD-T-README.md
CHANGED
|
@@ -231,20 +231,42 @@ GSD-T auto-detects your project's tech stack and injects mandatory best-practice
|
|
|
231
231
|
### How It Works
|
|
232
232
|
|
|
233
233
|
1. At subagent spawn time, GSD-T reads project manifest files to detect the active stack(s).
|
|
234
|
-
2. Universal rules (`templates/stacks/_security.md`) are **always** injected.
|
|
234
|
+
2. Universal rules (`templates/stacks/_security.md`, `_auth.md`) are **always** injected.
|
|
235
235
|
3. Stack-specific rules are injected when the corresponding stack is detected.
|
|
236
|
-
4.
|
|
236
|
+
4. Project-level overrides in `.gsd-t/stacks/` replace global files of the same name.
|
|
237
|
+
5. Rules are appended to the subagent prompt as a `## Stack Rules (MANDATORY)` section.
|
|
237
238
|
|
|
238
|
-
### Stack Detection
|
|
239
|
+
### Stack Detection (27 files)
|
|
239
240
|
|
|
240
|
-
| Project File | Detected Stack |
|
|
241
|
+
| Project File | Detected Stack(s) |
|
|
241
242
|
|---|---|
|
|
242
|
-
| `
|
|
243
|
-
| `package.json` with `"
|
|
244
|
-
| `package.json` with `"
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
243
|
+
| *(always)* | `_security.md`, `_auth.md` |
|
|
244
|
+
| `package.json` with `"react"` | `react.md` |
|
|
245
|
+
| `package.json` with `"react-native"` | `react-native.md` |
|
|
246
|
+
| `package.json` with `"next"` | `nextjs.md` |
|
|
247
|
+
| `package.json` with `"vue"` | `vue.md` |
|
|
248
|
+
| `package.json` with `"typescript"` or `tsconfig.json` | `typescript.md` |
|
|
249
|
+
| `package.json` with `"tailwindcss"` | `tailwind.md` |
|
|
250
|
+
| `package.json` with `"express"`, `"fastify"`, `"hono"`, or `"koa"` | `node-api.md`, `rest-api.md` |
|
|
251
|
+
| `package.json` with `"vite"` | `vite.md` |
|
|
252
|
+
| `package.json` with `"@supabase/supabase-js"` | `supabase.md` |
|
|
253
|
+
| `package.json` with `"firebase"` | `firebase.md` |
|
|
254
|
+
| `package.json` with `"graphql"` or `"@apollo/server"` | `graphql.md` |
|
|
255
|
+
| `package.json` with `"zustand"` | `zustand.md` |
|
|
256
|
+
| `package.json` with `"@reduxjs/toolkit"` | `redux.md` |
|
|
257
|
+
| `package.json` with `"prisma"` or `"@prisma/client"` | `prisma.md` |
|
|
258
|
+
| `package.json` with `"pg"`, `"knex"`, or `"drizzle-orm"` | `postgresql.md` |
|
|
259
|
+
| `package.json` with `"neo4j-driver"` | `neo4j.md` |
|
|
260
|
+
| `package.json` with `"bullmq"`, `"bull"`, `"amqplib"`, or `"@aws-sdk/client-sqs"` | `queues.md` |
|
|
261
|
+
| `package.json` with `"openai"`, `"anthropic"`, `"langchain"` | `llm.md` |
|
|
262
|
+
| `Dockerfile` or `compose.yaml` | `docker.md` |
|
|
263
|
+
| `.github/workflows/*.yml` | `github-actions.md` |
|
|
264
|
+
| `playwright.config.*` | `playwright.md` |
|
|
265
|
+
| `requirements.txt` or `pyproject.toml` | `python.md` |
|
|
266
|
+
| `requirements.txt` with `fastapi` | `fastapi.md` |
|
|
267
|
+
| `requirements.txt` with `celery`, `dramatiq`, `rq`, or `arq` | `queues.md` |
|
|
268
|
+
| `requirements.txt` with `openai`, `anthropic`, `langchain` | `llm.md` |
|
|
269
|
+
| `pubspec.yaml` | `flutter.md` |
|
|
248
270
|
|
|
249
271
|
### Commands That Inject Stack Rules
|
|
250
272
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "2.50.
|
|
3
|
+
"version": "2.50.12",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 51 slash commands with headless CI/CD mode, graph-powered code analysis, real-time agent dashboard, execution intelligence, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Authentication Standards (Universal — All Projects)
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Registration — Email-First, Password-Later
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Signup collects email + app-specific fields only — NO password at registration
|
|
12
|
+
├── After signup, send a "set your password" email (same flow as forgot password)
|
|
13
|
+
├── The set-password link is a one-time, time-limited token (15-60 minutes)
|
|
14
|
+
├── User clicks link → lands on set-password page → sets their password → logged in
|
|
15
|
+
├── This eliminates: temporary passwords, forced password changes, password-in-transit risks
|
|
16
|
+
└── If the link expires, user can request a new one (same as forgot password)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Why one flow**: "Set password" and "Forgot password" are the same operation — generate a token, email a link, user sets a password. Building them as one flow halves the auth code and guarantees consistent behavior.
|
|
20
|
+
|
|
21
|
+
**GOOD**
|
|
22
|
+
```
|
|
23
|
+
1. User submits: { email, name, [app-specific fields] }
|
|
24
|
+
2. Server creates account (no password set, status: pending_verification)
|
|
25
|
+
3. Server sends email: "Welcome! Set your password: [link with token]"
|
|
26
|
+
4. User clicks link → set-password page
|
|
27
|
+
5. User sets password → account activated → logged in
|
|
28
|
+
6. If link expires → user clicks "Resend" → same flow
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Provider Abstraction
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
MANDATORY:
|
|
37
|
+
├── Wrap the auth provider behind an AuthService interface
|
|
38
|
+
├── Application code NEVER calls Cognito/Firebase/Google/Supabase Auth directly
|
|
39
|
+
├── AuthService exposes: signup, login, logout, resetPassword, refreshToken, getCurrentUser
|
|
40
|
+
├── Switching providers = rewrite AuthService implementation, not the entire app
|
|
41
|
+
└── Auth provider config (pool IDs, client IDs, URLs) lives in env vars — not in code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**GOOD**
|
|
45
|
+
```typescript
|
|
46
|
+
// auth/AuthService.ts — interface
|
|
47
|
+
interface AuthService {
|
|
48
|
+
signup(email: string, metadata?: Record<string, string>): Promise<{ userId: string }>;
|
|
49
|
+
login(email: string, password: string): Promise<AuthTokens>;
|
|
50
|
+
logout(): Promise<void>;
|
|
51
|
+
sendPasswordResetEmail(email: string): Promise<void>;
|
|
52
|
+
setPassword(token: string, newPassword: string): Promise<void>;
|
|
53
|
+
refreshToken(refreshToken: string): Promise<AuthTokens>;
|
|
54
|
+
getCurrentUser(): Promise<User | null>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// auth/providers/CognitoAuthService.ts — implementation
|
|
58
|
+
// auth/providers/FirebaseAuthService.ts — implementation
|
|
59
|
+
// auth/providers/SupabaseAuthService.ts — implementation
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**BAD** — calling provider directly in components:
|
|
63
|
+
```typescript
|
|
64
|
+
import { signInWithEmailAndPassword } from 'firebase/auth';
|
|
65
|
+
// Scattered across 15 components, impossible to switch providers
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. Token Management
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
MANDATORY:
|
|
74
|
+
├── Access tokens: short-lived (15-60 minutes)
|
|
75
|
+
├── Refresh tokens: longer-lived (7-30 days), used to get new access tokens
|
|
76
|
+
├── Web: store tokens in httpOnly, secure, sameSite cookies — NEVER localStorage
|
|
77
|
+
├── Mobile (React Native/Flutter): use platform secure storage (Keychain/Keystore)
|
|
78
|
+
├── Auto-refresh: intercept 401 responses, refresh token, retry the request
|
|
79
|
+
├── NEVER store tokens in JavaScript-accessible storage (localStorage, sessionStorage)
|
|
80
|
+
└── NEVER send tokens in URL query parameters
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**GOOD** — auto-refresh interceptor:
|
|
84
|
+
```typescript
|
|
85
|
+
api.interceptors.response.use(
|
|
86
|
+
(response) => response,
|
|
87
|
+
async (error) => {
|
|
88
|
+
if (error.response?.status === 401 && !error.config._retry) {
|
|
89
|
+
error.config._retry = true;
|
|
90
|
+
const newTokens = await authService.refreshToken(getRefreshToken());
|
|
91
|
+
setTokens(newTokens);
|
|
92
|
+
error.config.headers.Authorization = `Bearer ${newTokens.accessToken}`;
|
|
93
|
+
return api(error.config);
|
|
94
|
+
}
|
|
95
|
+
return Promise.reject(error);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. Password Policy
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
MANDATORY:
|
|
106
|
+
├── Minimum 8 characters — no maximum (allow up to 128)
|
|
107
|
+
├── Require mix of: uppercase, lowercase, number, special character
|
|
108
|
+
├── Check against common password list (top 10,000) — reject "Password123!"
|
|
109
|
+
├── Show password strength indicator in real-time as user types
|
|
110
|
+
├── Allow paste into password fields — NEVER disable paste
|
|
111
|
+
├── NEVER store plaintext passwords — hashing is server-side only (bcrypt/argon2)
|
|
112
|
+
├── NEVER transmit password requirements to the client beyond the UI hints
|
|
113
|
+
└── Rate-limit login attempts: lock after 5 failed attempts for 15 minutes
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 5. Session Management
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
MANDATORY:
|
|
122
|
+
├── Logout clears ALL state: tokens, cached user data, in-memory state
|
|
123
|
+
├── Logout invalidates the refresh token server-side (not just client delete)
|
|
124
|
+
├── Session timeout: auto-logout after inactivity period (configurable per app)
|
|
125
|
+
├── Multi-tab sync (web): logout in one tab logs out all tabs
|
|
126
|
+
├── Token expiry: show "session expired" message, redirect to login
|
|
127
|
+
├── NEVER keep stale auth state — if token refresh fails, force logout
|
|
128
|
+
└── On app startup: validate the stored token before showing authenticated UI
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**GOOD** — multi-tab logout sync:
|
|
132
|
+
```typescript
|
|
133
|
+
// Listen for storage events (fires when another tab changes storage)
|
|
134
|
+
window.addEventListener('storage', (event) => {
|
|
135
|
+
if (event.key === 'logout-event') {
|
|
136
|
+
authService.clearLocalState();
|
|
137
|
+
window.location.href = '/login';
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// On logout, broadcast to other tabs
|
|
142
|
+
function logout() {
|
|
143
|
+
localStorage.setItem('logout-event', Date.now().toString());
|
|
144
|
+
localStorage.removeItem('logout-event');
|
|
145
|
+
authService.logout();
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 6. Social Auth / OAuth
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
WHEN SUPPORTING SOCIAL LOGIN:
|
|
155
|
+
├── Use OAuth 2.0 / OpenID Connect — NEVER custom social auth flows
|
|
156
|
+
├── Handle "email already exists" — offer to link accounts, don't create duplicates
|
|
157
|
+
├── Store the provider + provider user ID alongside the local account
|
|
158
|
+
├── Social login creates a local account on first use (same as email signup, but pre-verified)
|
|
159
|
+
├── Allow users to set a password later (to enable email+password login alongside social)
|
|
160
|
+
├── NEVER trust the email from the OAuth provider without verifying it's the same user
|
|
161
|
+
└── Implement PKCE for public clients (SPAs, mobile apps)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Account linking flow:**
|
|
165
|
+
```
|
|
166
|
+
1. User clicks "Sign in with Google" → OAuth flow → returns email: jane@example.com
|
|
167
|
+
2. Server checks: does jane@example.com already have an account?
|
|
168
|
+
YES → Link Google provider to existing account → login
|
|
169
|
+
NO → Create new account with Google as primary provider → login
|
|
170
|
+
3. User can later set a password to also use email+password login
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 7. Email Verification
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
MANDATORY:
|
|
179
|
+
├── Email must be verified before account is fully activated
|
|
180
|
+
├── Verification link = one-time token, expires in 24 hours
|
|
181
|
+
├── Unverified accounts: allow login but restrict access (show "verify your email" banner)
|
|
182
|
+
├── Resend verification: rate-limit to 3 per hour
|
|
183
|
+
├── After email change: re-verify the new email before switching
|
|
184
|
+
└── NEVER expose whether an email is registered (prevents enumeration)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Anti-enumeration pattern:**
|
|
188
|
+
```
|
|
189
|
+
// BAD — reveals whether email exists
|
|
190
|
+
"No account found for jane@example.com"
|
|
191
|
+
|
|
192
|
+
// GOOD — same message regardless
|
|
193
|
+
"If an account exists for this email, you'll receive a password reset link"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 8. Multi-Factor Authentication (MFA)
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
WHEN MFA IS REQUIRED:
|
|
202
|
+
├── Support TOTP (authenticator app) as primary — SMS as fallback only
|
|
203
|
+
├── Provide recovery codes (8-10 one-time codes) during MFA setup
|
|
204
|
+
├── Store recovery codes hashed — display only once during setup
|
|
205
|
+
├── MFA enrollment: optional by default, mandatory for admin/elevated roles
|
|
206
|
+
├── Remember device: offer "trust this device for 30 days" option
|
|
207
|
+
└── NEVER use email as a second factor — it's the same channel as password reset
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 9. Authorization Patterns
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
MANDATORY:
|
|
216
|
+
├── Role-based access control (RBAC): define roles with explicit permissions
|
|
217
|
+
├── Check permissions server-side on EVERY request — client checks are UI hints only
|
|
218
|
+
├── Define roles as enums: ADMIN, MEMBER, VIEWER — not strings
|
|
219
|
+
├── Permission check: can(user, action, resource) — not role string comparison
|
|
220
|
+
├── UI: hide or disable actions the user can't perform — don't show then reject
|
|
221
|
+
├── API: return 403 Forbidden for unauthorized actions — not 404
|
|
222
|
+
└── NEVER derive permissions from user properties — use explicit role → permission mapping
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**GOOD**
|
|
226
|
+
```typescript
|
|
227
|
+
// Define permissions explicitly
|
|
228
|
+
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
|
|
229
|
+
[UserRole.ADMIN]: ['users:read', 'users:write', 'users:delete', 'settings:write'],
|
|
230
|
+
[UserRole.MEMBER]: ['users:read', 'users:write'],
|
|
231
|
+
[UserRole.VIEWER]: ['users:read'],
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function can(user: User, permission: Permission): boolean {
|
|
235
|
+
return ROLE_PERMISSIONS[user.role]?.includes(permission) ?? false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// In API handler
|
|
239
|
+
if (!can(currentUser, 'users:delete')) {
|
|
240
|
+
throw new ForbiddenError('Insufficient permissions');
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 10. Auth-Related Security
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
MANDATORY:
|
|
250
|
+
├── All auth endpoints over HTTPS — no exceptions
|
|
251
|
+
├── CSRF protection on auth forms (use framework's built-in CSRF tokens)
|
|
252
|
+
├── Brute force protection: rate-limit login, lock after N failures
|
|
253
|
+
├── Password reset tokens: single-use, time-limited, cryptographically random
|
|
254
|
+
├── Log all auth events: login, logout, failed attempts, password changes, MFA events
|
|
255
|
+
├── NEVER log passwords, tokens, or session IDs — even in error logs
|
|
256
|
+
├── NEVER include auth tokens in URLs — use headers or cookies
|
|
257
|
+
└── Rotate signing keys periodically (JWT secret, cookie signing key)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 11. Auth UI Patterns
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
MANDATORY:
|
|
266
|
+
├── Login form: email + password + "Forgot password?" link + social login buttons
|
|
267
|
+
├── Signup form: email + app-specific fields + "Already have an account?" link
|
|
268
|
+
├── Forgot password: email input → "Check your email" (same message regardless of email existence)
|
|
269
|
+
├── Set password: new password + confirm password + strength indicator
|
|
270
|
+
├── Loading state on all auth buttons — disable during submission
|
|
271
|
+
├── Show/hide password toggle on password fields
|
|
272
|
+
├── Preserve form data on validation errors — don't clear the form
|
|
273
|
+
└── Redirect to intended destination after login (not always to homepage)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Redirect after login:**
|
|
277
|
+
```typescript
|
|
278
|
+
// Before redirecting to login, store the intended URL
|
|
279
|
+
const returnUrl = window.location.pathname;
|
|
280
|
+
router.push(`/login?returnUrl=${encodeURIComponent(returnUrl)}`);
|
|
281
|
+
|
|
282
|
+
// After successful login
|
|
283
|
+
const returnUrl = searchParams.get('returnUrl') || '/dashboard';
|
|
284
|
+
router.push(returnUrl);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 12. Anti-Patterns
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
NEVER:
|
|
293
|
+
├── Password at registration — use email-first, set-password-later flow
|
|
294
|
+
├── Tokens in localStorage — use httpOnly cookies (web) or secure storage (mobile)
|
|
295
|
+
├── Tokens in URL query parameters
|
|
296
|
+
├── Calling auth provider directly from components — use AuthService abstraction
|
|
297
|
+
├── Revealing whether an email is registered ("no account found for...")
|
|
298
|
+
├── Client-side-only permission checks — always enforce server-side
|
|
299
|
+
├── Same message for login failure types ("wrong password" vs "account locked")
|
|
300
|
+
├── Disabling paste on password fields
|
|
301
|
+
├── Email as a second factor for MFA
|
|
302
|
+
├── Storing plaintext passwords or unhashed recovery codes
|
|
303
|
+
└── Silent token expiry — always inform the user and redirect to login
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Auth Verification Checklist
|
|
309
|
+
|
|
310
|
+
- [ ] Signup is email-first — no password at registration
|
|
311
|
+
- [ ] Set-password and forgot-password share the same token-based flow
|
|
312
|
+
- [ ] Auth provider wrapped in AuthService interface — no direct provider calls
|
|
313
|
+
- [ ] Tokens stored in httpOnly cookies (web) or secure storage (mobile)
|
|
314
|
+
- [ ] Access tokens short-lived with auto-refresh on 401
|
|
315
|
+
- [ ] Logout clears all state and invalidates refresh token server-side
|
|
316
|
+
- [ ] Multi-tab logout sync (web)
|
|
317
|
+
- [ ] Password policy enforced with strength indicator
|
|
318
|
+
- [ ] Login rate-limited — lock after 5 failed attempts
|
|
319
|
+
- [ ] Email enumeration prevented (same response for existing/non-existing emails)
|
|
320
|
+
- [ ] Social auth handles account linking for existing emails
|
|
321
|
+
- [ ] Permissions checked server-side on every request
|
|
322
|
+
- [ ] Roles defined as enums with explicit permission mapping
|
|
323
|
+
- [ ] All auth events logged (no tokens/passwords in logs)
|
|
324
|
+
- [ ] Redirect to intended destination after login
|