javi-forge 1.2.0 → 1.3.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.
- package/ci-local/ci-local.sh +20 -8
- package/package.json +1 -1
- package/ai-config/.skillignore +0 -15
- package/ai-config/AUTO_INVOKE.md +0 -300
- package/ai-config/agents/_TEMPLATE.md +0 -93
- package/ai-config/agents/business/api-designer.md +0 -1657
- package/ai-config/agents/business/business-analyst.md +0 -1331
- package/ai-config/agents/business/product-strategist.md +0 -206
- package/ai-config/agents/business/project-manager.md +0 -178
- package/ai-config/agents/business/requirements-analyst.md +0 -1277
- package/ai-config/agents/business/technical-writer.md +0 -1679
- package/ai-config/agents/creative/ux-designer.md +0 -205
- package/ai-config/agents/data-ai/ai-engineer.md +0 -487
- package/ai-config/agents/data-ai/analytics-engineer.md +0 -953
- package/ai-config/agents/data-ai/data-engineer.md +0 -173
- package/ai-config/agents/data-ai/data-scientist.md +0 -672
- package/ai-config/agents/data-ai/mlops-engineer.md +0 -814
- package/ai-config/agents/data-ai/prompt-engineer.md +0 -772
- package/ai-config/agents/development/angular-expert.md +0 -620
- package/ai-config/agents/development/backend-architect.md +0 -795
- package/ai-config/agents/development/database-specialist.md +0 -212
- package/ai-config/agents/development/frontend-specialist.md +0 -686
- package/ai-config/agents/development/fullstack-engineer.md +0 -668
- package/ai-config/agents/development/golang-pro.md +0 -338
- package/ai-config/agents/development/java-enterprise.md +0 -400
- package/ai-config/agents/development/javascript-pro.md +0 -422
- package/ai-config/agents/development/nextjs-pro.md +0 -474
- package/ai-config/agents/development/python-pro.md +0 -570
- package/ai-config/agents/development/react-pro.md +0 -487
- package/ai-config/agents/development/rust-pro.md +0 -246
- package/ai-config/agents/development/spring-boot-4-expert.md +0 -326
- package/ai-config/agents/development/typescript-pro.md +0 -336
- package/ai-config/agents/development/vue-specialist.md +0 -605
- package/ai-config/agents/infrastructure/cloud-architect.md +0 -472
- package/ai-config/agents/infrastructure/deployment-manager.md +0 -358
- package/ai-config/agents/infrastructure/devops-engineer.md +0 -455
- package/ai-config/agents/infrastructure/incident-responder.md +0 -519
- package/ai-config/agents/infrastructure/kubernetes-expert.md +0 -705
- package/ai-config/agents/infrastructure/monitoring-specialist.md +0 -674
- package/ai-config/agents/infrastructure/performance-engineer.md +0 -658
- package/ai-config/agents/orchestrator.md +0 -241
- package/ai-config/agents/quality/accessibility-auditor.md +0 -1204
- package/ai-config/agents/quality/code-reviewer-compact.md +0 -123
- package/ai-config/agents/quality/code-reviewer.md +0 -363
- package/ai-config/agents/quality/dependency-manager.md +0 -743
- package/ai-config/agents/quality/e2e-test-specialist.md +0 -1005
- package/ai-config/agents/quality/performance-tester.md +0 -1086
- package/ai-config/agents/quality/security-auditor.md +0 -133
- package/ai-config/agents/quality/test-engineer.md +0 -453
- package/ai-config/agents/specialists/api-designer.md +0 -87
- package/ai-config/agents/specialists/backend-architect.md +0 -73
- package/ai-config/agents/specialists/code-reviewer.md +0 -77
- package/ai-config/agents/specialists/db-optimizer.md +0 -75
- package/ai-config/agents/specialists/devops-engineer.md +0 -83
- package/ai-config/agents/specialists/documentation-writer.md +0 -78
- package/ai-config/agents/specialists/frontend-developer.md +0 -75
- package/ai-config/agents/specialists/performance-analyst.md +0 -82
- package/ai-config/agents/specialists/refactor-specialist.md +0 -74
- package/ai-config/agents/specialists/security-auditor.md +0 -74
- package/ai-config/agents/specialists/test-engineer.md +0 -81
- package/ai-config/agents/specialists/ux-consultant.md +0 -76
- package/ai-config/agents/specialized/agent-generator.md +0 -1190
- package/ai-config/agents/specialized/blockchain-developer.md +0 -149
- package/ai-config/agents/specialized/code-migrator.md +0 -892
- package/ai-config/agents/specialized/context-manager.md +0 -978
- package/ai-config/agents/specialized/documentation-writer.md +0 -1078
- package/ai-config/agents/specialized/ecommerce-expert.md +0 -1756
- package/ai-config/agents/specialized/embedded-engineer.md +0 -1714
- package/ai-config/agents/specialized/error-detective.md +0 -1034
- package/ai-config/agents/specialized/fintech-specialist.md +0 -1659
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +0 -1988
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +0 -2136
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +0 -4503
- package/ai-config/agents/specialized/freelance-project-planner.md +0 -722
- package/ai-config/agents/specialized/game-developer.md +0 -1963
- package/ai-config/agents/specialized/healthcare-dev.md +0 -1620
- package/ai-config/agents/specialized/mobile-developer.md +0 -188
- package/ai-config/agents/specialized/parallel-plan-executor.md +0 -506
- package/ai-config/agents/specialized/plan-executor.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +0 -3493
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +0 -778
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +0 -918
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +0 -1537
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +0 -2633
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +0 -5610
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +0 -335
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +0 -215
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +0 -260
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +0 -379
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +0 -355
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +0 -279
- package/ai-config/agents/specialized/template-writer.md +0 -347
- package/ai-config/agents/specialized/test-runner.md +0 -99
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +0 -244
- package/ai-config/agents/specialized/wave-executor.md +0 -138
- package/ai-config/agents/specialized/workflow-optimizer.md +0 -1114
- package/ai-config/commands/git/changelog.md +0 -32
- package/ai-config/commands/git/ci-local.md +0 -70
- package/ai-config/commands/git/commit.md +0 -35
- package/ai-config/commands/git/fix-issue.md +0 -23
- package/ai-config/commands/git/pr-create.md +0 -42
- package/ai-config/commands/git/pr-review.md +0 -50
- package/ai-config/commands/git/worktree.md +0 -39
- package/ai-config/commands/refactoring/cleanup.md +0 -24
- package/ai-config/commands/refactoring/dead-code.md +0 -40
- package/ai-config/commands/refactoring/extract.md +0 -31
- package/ai-config/commands/testing/e2e.md +0 -30
- package/ai-config/commands/testing/tdd.md +0 -36
- package/ai-config/commands/testing/test-coverage.md +0 -30
- package/ai-config/commands/testing/test-fix.md +0 -24
- package/ai-config/commands/workflow/generate-agents-md.md +0 -85
- package/ai-config/commands/workflow/planning.md +0 -47
- package/ai-config/commands/workflows/compound.md +0 -89
- package/ai-config/commands/workflows/diagnose.md +0 -70
- package/ai-config/commands/workflows/discover.md +0 -86
- package/ai-config/commands/workflows/plan.md +0 -77
- package/ai-config/commands/workflows/review.md +0 -78
- package/ai-config/commands/workflows/work.md +0 -75
- package/ai-config/config.yaml +0 -18
- package/ai-config/hooks/_TEMPLATE.md +0 -96
- package/ai-config/hooks/block-dangerous-commands.md +0 -75
- package/ai-config/hooks/commit-guard.md +0 -90
- package/ai-config/hooks/context-loader.md +0 -73
- package/ai-config/hooks/improve-prompt.md +0 -91
- package/ai-config/hooks/learning-log.md +0 -72
- package/ai-config/hooks/model-router.md +0 -86
- package/ai-config/hooks/secret-scanner.md +0 -64
- package/ai-config/hooks/skill-validator.md +0 -102
- package/ai-config/hooks/task-artifact.md +0 -114
- package/ai-config/hooks/validate-workflow.md +0 -100
- package/ai-config/prompts/base.md +0 -71
- package/ai-config/prompts/modes/debug.md +0 -34
- package/ai-config/prompts/modes/deploy.md +0 -40
- package/ai-config/prompts/modes/research.md +0 -32
- package/ai-config/prompts/modes/review.md +0 -33
- package/ai-config/prompts/review-policy.md +0 -79
- package/ai-config/skills/_TEMPLATE.md +0 -157
- package/ai-config/skills/backend/api-gateway/SKILL.md +0 -254
- package/ai-config/skills/backend/bff-concepts/SKILL.md +0 -239
- package/ai-config/skills/backend/bff-spring/SKILL.md +0 -364
- package/ai-config/skills/backend/chi-router/SKILL.md +0 -396
- package/ai-config/skills/backend/error-handling/SKILL.md +0 -255
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +0 -323
- package/ai-config/skills/backend/fastapi/SKILL.md +0 -302
- package/ai-config/skills/backend/gateway-spring/SKILL.md +0 -390
- package/ai-config/skills/backend/go-backend/SKILL.md +0 -457
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +0 -274
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +0 -352
- package/ai-config/skills/backend/graphql-spring/SKILL.md +0 -398
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +0 -283
- package/ai-config/skills/backend/grpc-spring/SKILL.md +0 -445
- package/ai-config/skills/backend/jwt-auth/SKILL.md +0 -412
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +0 -259
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +0 -261
- package/ai-config/skills/backend/search-concepts/SKILL.md +0 -263
- package/ai-config/skills/backend/search-spring/SKILL.md +0 -375
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +0 -172
- package/ai-config/skills/backend/websockets/SKILL.md +0 -532
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +0 -423
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +0 -195
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +0 -340
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +0 -440
- package/ai-config/skills/data-ai/langchain/SKILL.md +0 -238
- package/ai-config/skills/data-ai/mlflow/SKILL.md +0 -302
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +0 -290
- package/ai-config/skills/data-ai/powerbi/SKILL.md +0 -352
- package/ai-config/skills/data-ai/pytorch/SKILL.md +0 -274
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +0 -321
- package/ai-config/skills/data-ai/vector-db/SKILL.md +0 -301
- package/ai-config/skills/database/graph-databases/SKILL.md +0 -218
- package/ai-config/skills/database/graph-spring/SKILL.md +0 -361
- package/ai-config/skills/database/pgx-postgres/SKILL.md +0 -512
- package/ai-config/skills/database/redis-cache/SKILL.md +0 -343
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +0 -388
- package/ai-config/skills/database/timescaledb/SKILL.md +0 -320
- package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
- package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
- package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
- package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +0 -441
- package/ai-config/skills/frontend/frontend-design/SKILL.md +0 -54
- package/ai-config/skills/frontend/frontend-web/SKILL.md +0 -368
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +0 -396
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +0 -439
- package/ai-config/skills/frontend/zod-validation/SKILL.md +0 -417
- package/ai-config/skills/frontend/zustand-state/SKILL.md +0 -350
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +0 -244
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +0 -378
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +0 -435
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +0 -420
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +0 -456
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +0 -546
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +0 -474
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +0 -315
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +0 -504
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +0 -448
- package/ai-config/skills/prompt-improver/SKILL.md +0 -125
- package/ai-config/skills/quality/ghagga-review/SKILL.md +0 -216
- package/ai-config/skills/references/hooks-patterns/SKILL.md +0 -238
- package/ai-config/skills/references/mcp-servers/SKILL.md +0 -275
- package/ai-config/skills/references/plugins-reference/SKILL.md +0 -110
- package/ai-config/skills/references/skills-reference/SKILL.md +0 -420
- package/ai-config/skills/references/subagent-templates/SKILL.md +0 -193
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +0 -410
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +0 -408
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +0 -386
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +0 -324
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +0 -289
- package/ai-config/skills/testing/testcontainers/SKILL.md +0 -299
- package/ai-config/skills/testing/vitest-testing/SKILL.md +0 -381
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +0 -118
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +0 -299
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +0 -158
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +0 -117
- package/ai-config/skills/workflow/git-github/SKILL.md +0 -334
- package/ai-config/skills/workflow/git-github/references/examples.md +0 -160
- package/ai-config/skills/workflow/git-workflow/SKILL.md +0 -214
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +0 -277
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +0 -401
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +0 -199
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +0 -100
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +0 -73
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +0 -178
- package/schemas/agent.schema.json +0 -34
- package/schemas/ai-config.schema.json +0 -28
- package/schemas/plugin.schema.json +0 -62
- package/schemas/skill.schema.json +0 -44
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: jwt-auth
|
|
3
|
-
description: >
|
|
4
|
-
JWT authentication with access/refresh tokens, RBAC, and multi-tenant support.
|
|
5
|
-
Trigger: jwt, authentication, auth, token, rbac, authorization, login
|
|
6
|
-
tools:
|
|
7
|
-
- Read
|
|
8
|
-
- Write
|
|
9
|
-
- Bash
|
|
10
|
-
- Grep
|
|
11
|
-
metadata:
|
|
12
|
-
author: plataforma-industrial
|
|
13
|
-
version: "2.0"
|
|
14
|
-
tags: [security, jwt, auth, rbac]
|
|
15
|
-
updated: "2026-02"
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# JWT Authentication Skill
|
|
19
|
-
|
|
20
|
-
## Stack
|
|
21
|
-
|
|
22
|
-
```yaml
|
|
23
|
-
# Go
|
|
24
|
-
golang-jwt/jwt: v5
|
|
25
|
-
bcrypt: golang.org/x/crypto/bcrypt
|
|
26
|
-
|
|
27
|
-
# TypeScript
|
|
28
|
-
jose: 5.2+
|
|
29
|
-
|
|
30
|
-
# Python
|
|
31
|
-
PyJWT: 2.8+
|
|
32
|
-
passlib: 1.7+
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Token Structure
|
|
36
|
-
|
|
37
|
-
### Claims
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"iss": "app-name",
|
|
42
|
-
"sub": "user-123",
|
|
43
|
-
"aud": ["api"],
|
|
44
|
-
"exp": 1704067200,
|
|
45
|
-
"iat": 1704063600,
|
|
46
|
-
"uid": "user-123",
|
|
47
|
-
"tid": "tenant-456",
|
|
48
|
-
"role": "operator",
|
|
49
|
-
"scopes": ["items:read", "items:write", "alerts:read"]
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Go Implementation
|
|
54
|
-
|
|
55
|
-
### JWT Service
|
|
56
|
-
|
|
57
|
-
```go
|
|
58
|
-
// auth/jwt.go
|
|
59
|
-
package auth
|
|
60
|
-
|
|
61
|
-
import (
|
|
62
|
-
"time"
|
|
63
|
-
"github.com/golang-jwt/jwt/v5"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
type AccessTokenClaims struct {
|
|
67
|
-
jwt.RegisteredClaims
|
|
68
|
-
UserID string `json:"uid"`
|
|
69
|
-
TenantID string `json:"tid"`
|
|
70
|
-
Role string `json:"role"`
|
|
71
|
-
Scopes []string `json:"scopes,omitempty"`
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
type JWTConfig struct {
|
|
75
|
-
AccessSecret []byte
|
|
76
|
-
RefreshSecret []byte
|
|
77
|
-
AccessDuration time.Duration // 15 minutes
|
|
78
|
-
RefreshDuration time.Duration // 7 days
|
|
79
|
-
Issuer string
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
type JWTService struct {
|
|
83
|
-
config JWTConfig
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func (s *JWTService) GenerateAccessToken(user *User) (string, error) {
|
|
87
|
-
now := time.Now()
|
|
88
|
-
claims := AccessTokenClaims{
|
|
89
|
-
RegisteredClaims: jwt.RegisteredClaims{
|
|
90
|
-
Issuer: s.config.Issuer,
|
|
91
|
-
Subject: user.ID,
|
|
92
|
-
ExpiresAt: jwt.NewNumericDate(now.Add(s.config.AccessDuration)),
|
|
93
|
-
IssuedAt: jwt.NewNumericDate(now),
|
|
94
|
-
},
|
|
95
|
-
UserID: user.ID,
|
|
96
|
-
TenantID: user.TenantID,
|
|
97
|
-
Role: user.Role,
|
|
98
|
-
Scopes: user.Scopes,
|
|
99
|
-
}
|
|
100
|
-
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
101
|
-
return token.SignedString(s.config.AccessSecret)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
func (s *JWTService) ValidateAccessToken(tokenString string) (*AccessTokenClaims, error) {
|
|
105
|
-
token, err := jwt.ParseWithClaims(
|
|
106
|
-
tokenString,
|
|
107
|
-
&AccessTokenClaims{},
|
|
108
|
-
func(token *jwt.Token) (interface{}, error) {
|
|
109
|
-
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
110
|
-
return nil, ErrInvalidToken
|
|
111
|
-
}
|
|
112
|
-
return s.config.AccessSecret, nil
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
if err != nil {
|
|
116
|
-
return nil, ErrInvalidToken
|
|
117
|
-
}
|
|
118
|
-
claims, ok := token.Claims.(*AccessTokenClaims)
|
|
119
|
-
if !ok || !token.Valid {
|
|
120
|
-
return nil, ErrInvalidClaims
|
|
121
|
-
}
|
|
122
|
-
return claims, nil
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Auth Middleware
|
|
127
|
-
|
|
128
|
-
```go
|
|
129
|
-
// middleware/auth.go
|
|
130
|
-
type contextKey string
|
|
131
|
-
|
|
132
|
-
const (
|
|
133
|
-
UserIDKey contextKey = "user_id"
|
|
134
|
-
TenantIDKey contextKey = "tenant_id"
|
|
135
|
-
RoleKey contextKey = "role"
|
|
136
|
-
ScopesKey contextKey = "scopes"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
func (m *AuthMiddleware) Authenticate(next http.Handler) http.Handler {
|
|
140
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
141
|
-
authHeader := r.Header.Get("Authorization")
|
|
142
|
-
if !strings.HasPrefix(authHeader, "Bearer ") {
|
|
143
|
-
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
claims, err := m.jwtService.ValidateAccessToken(authHeader[7:])
|
|
148
|
-
if err != nil {
|
|
149
|
-
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
ctx := r.Context()
|
|
154
|
-
ctx = context.WithValue(ctx, UserIDKey, claims.UserID)
|
|
155
|
-
ctx = context.WithValue(ctx, TenantIDKey, claims.TenantID)
|
|
156
|
-
ctx = context.WithValue(ctx, RoleKey, claims.Role)
|
|
157
|
-
ctx = context.WithValue(ctx, ScopesKey, claims.Scopes)
|
|
158
|
-
|
|
159
|
-
next.ServeHTTP(w, r.WithContext(ctx))
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
func (m *AuthMiddleware) RequireRole(roles ...string) func(http.Handler) http.Handler {
|
|
164
|
-
return func(next http.Handler) http.Handler {
|
|
165
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
166
|
-
role := r.Context().Value(RoleKey).(string)
|
|
167
|
-
for _, allowed := range roles {
|
|
168
|
-
if role == allowed {
|
|
169
|
-
next.ServeHTTP(w, r)
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
func (m *AuthMiddleware) RequireScope(required string) func(http.Handler) http.Handler {
|
|
179
|
-
return func(next http.Handler) http.Handler {
|
|
180
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
181
|
-
scopes := r.Context().Value(ScopesKey).([]string)
|
|
182
|
-
for _, scope := range scopes {
|
|
183
|
-
if scope == required || scope == "*" {
|
|
184
|
-
next.ServeHTTP(w, r)
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
http.Error(w, "Insufficient permissions", http.StatusForbidden)
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Helpers
|
|
194
|
-
func GetUserID(ctx context.Context) string {
|
|
195
|
-
if v := ctx.Value(UserIDKey); v != nil {
|
|
196
|
-
return v.(string)
|
|
197
|
-
}
|
|
198
|
-
return ""
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
func GetTenantID(ctx context.Context) string {
|
|
202
|
-
if v := ctx.Value(TenantIDKey); v != nil {
|
|
203
|
-
return v.(string)
|
|
204
|
-
}
|
|
205
|
-
return ""
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Login Handler
|
|
210
|
-
|
|
211
|
-
```go
|
|
212
|
-
type TokenResponse struct {
|
|
213
|
-
AccessToken string `json:"access_token"`
|
|
214
|
-
RefreshToken string `json:"refresh_token"`
|
|
215
|
-
ExpiresIn int `json:"expires_in"`
|
|
216
|
-
TokenType string `json:"token_type"`
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|
220
|
-
var req LoginRequest
|
|
221
|
-
json.NewDecoder(r.Body).Decode(&req)
|
|
222
|
-
|
|
223
|
-
user, _ := h.userRepo.GetByEmail(r.Context(), req.Email)
|
|
224
|
-
|
|
225
|
-
if err := bcrypt.CompareHashAndPassword(
|
|
226
|
-
[]byte(user.PasswordHash), []byte(req.Password),
|
|
227
|
-
); err != nil {
|
|
228
|
-
respondError(w, http.StatusUnauthorized, "Invalid credentials")
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
accessToken, _ := h.jwtService.GenerateAccessToken(user)
|
|
233
|
-
refreshToken, tokenID, _ := h.jwtService.GenerateRefreshToken(user)
|
|
234
|
-
|
|
235
|
-
h.tokenStore.Store(r.Context(), tokenID, user.ID, 7*24*time.Hour)
|
|
236
|
-
|
|
237
|
-
respondJSON(w, http.StatusOK, TokenResponse{
|
|
238
|
-
AccessToken: accessToken,
|
|
239
|
-
RefreshToken: refreshToken,
|
|
240
|
-
ExpiresIn: 900,
|
|
241
|
-
TokenType: "Bearer",
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## TypeScript Client
|
|
247
|
-
|
|
248
|
-
### Auth Store (Zustand)
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
import { create } from 'zustand';
|
|
252
|
-
import { persist } from 'zustand/middleware';
|
|
253
|
-
import { jwtDecode } from 'jwt-decode';
|
|
254
|
-
|
|
255
|
-
interface TokenPayload {
|
|
256
|
-
uid: string;
|
|
257
|
-
tid: string;
|
|
258
|
-
role: string;
|
|
259
|
-
scopes: string[];
|
|
260
|
-
exp: number;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
interface AuthState {
|
|
264
|
-
accessToken: string | null;
|
|
265
|
-
refreshToken: string | null;
|
|
266
|
-
user: TokenPayload | null;
|
|
267
|
-
isAuthenticated: boolean;
|
|
268
|
-
setTokens: (access: string, refresh: string) => void;
|
|
269
|
-
logout: () => void;
|
|
270
|
-
isTokenExpired: () => boolean;
|
|
271
|
-
hasScope: (scope: string) => boolean;
|
|
272
|
-
hasRole: (role: string) => boolean;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export const useAuthStore = create<AuthState>()(
|
|
276
|
-
persist(
|
|
277
|
-
(set, get) => ({
|
|
278
|
-
accessToken: null,
|
|
279
|
-
refreshToken: null,
|
|
280
|
-
user: null,
|
|
281
|
-
isAuthenticated: false,
|
|
282
|
-
|
|
283
|
-
setTokens: (accessToken, refreshToken) => {
|
|
284
|
-
const payload = jwtDecode<TokenPayload>(accessToken);
|
|
285
|
-
set({ accessToken, refreshToken, user: payload, isAuthenticated: true });
|
|
286
|
-
},
|
|
287
|
-
|
|
288
|
-
logout: () => set({
|
|
289
|
-
accessToken: null, refreshToken: null, user: null, isAuthenticated: false
|
|
290
|
-
}),
|
|
291
|
-
|
|
292
|
-
isTokenExpired: () => {
|
|
293
|
-
const { user } = get();
|
|
294
|
-
return !user || Date.now() >= user.exp * 1000;
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
hasScope: (scope) => {
|
|
298
|
-
const { user } = get();
|
|
299
|
-
return user?.scopes.includes(scope) || user?.scopes.includes('*') || false;
|
|
300
|
-
},
|
|
301
|
-
|
|
302
|
-
hasRole: (role) => get().user?.role === role,
|
|
303
|
-
}),
|
|
304
|
-
{ name: 'auth-storage' }
|
|
305
|
-
)
|
|
306
|
-
);
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### API Client with Auto-Refresh
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
class ApiClient {
|
|
313
|
-
private refreshPromise: Promise<void> | null = null;
|
|
314
|
-
|
|
315
|
-
async fetch<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
316
|
-
const { accessToken, isTokenExpired } = useAuthStore.getState();
|
|
317
|
-
|
|
318
|
-
if (accessToken && isTokenExpired()) {
|
|
319
|
-
await this.refreshToken();
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const response = await this.makeRequest(path, options);
|
|
323
|
-
|
|
324
|
-
if (response.status === 401) {
|
|
325
|
-
await this.refreshToken();
|
|
326
|
-
return this.makeRequest(path, options).then(r => r.json());
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return response.json();
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private async refreshToken(): Promise<void> {
|
|
333
|
-
if (this.refreshPromise) return this.refreshPromise;
|
|
334
|
-
|
|
335
|
-
const { refreshToken, setTokens, logout } = useAuthStore.getState();
|
|
336
|
-
if (!refreshToken) { logout(); throw new Error('No refresh token'); }
|
|
337
|
-
|
|
338
|
-
this.refreshPromise = (async () => {
|
|
339
|
-
try {
|
|
340
|
-
const response = await fetch(`${API_URL}/auth/refresh`, {
|
|
341
|
-
method: 'POST',
|
|
342
|
-
headers: { 'Content-Type': 'application/json' },
|
|
343
|
-
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
344
|
-
});
|
|
345
|
-
if (!response.ok) { logout(); throw new Error('Refresh failed'); }
|
|
346
|
-
const data = await response.json();
|
|
347
|
-
setTokens(data.access_token, data.refresh_token);
|
|
348
|
-
} finally {
|
|
349
|
-
this.refreshPromise = null;
|
|
350
|
-
}
|
|
351
|
-
})();
|
|
352
|
-
|
|
353
|
-
return this.refreshPromise;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
### Protected Route (React)
|
|
359
|
-
|
|
360
|
-
```tsx
|
|
361
|
-
function ProtectedRoute({ children, requiredRole, requiredScope }: Props) {
|
|
362
|
-
const { isAuthenticated, hasRole, hasScope } = useAuthStore();
|
|
363
|
-
|
|
364
|
-
if (!isAuthenticated) return <Navigate to="/login" />;
|
|
365
|
-
if (requiredRole && !hasRole(requiredRole)) return <Navigate to="/unauthorized" />;
|
|
366
|
-
if (requiredScope && !hasScope(requiredScope)) return <Navigate to="/unauthorized" />;
|
|
367
|
-
|
|
368
|
-
return <>{children}</>;
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
## Database Schema
|
|
373
|
-
|
|
374
|
-
```sql
|
|
375
|
-
CREATE TABLE roles (
|
|
376
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
377
|
-
name TEXT NOT NULL UNIQUE,
|
|
378
|
-
scopes TEXT[] NOT NULL DEFAULT '{}'
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
INSERT INTO roles (name, scopes) VALUES
|
|
382
|
-
('viewer', ARRAY['items:read', 'dashboard:read']),
|
|
383
|
-
('operator', ARRAY['items:read', 'items:write', 'alerts:acknowledge']),
|
|
384
|
-
('admin', ARRAY['*']);
|
|
385
|
-
|
|
386
|
-
CREATE TABLE users (
|
|
387
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
388
|
-
tenant_id UUID NOT NULL REFERENCES tenants(id),
|
|
389
|
-
email TEXT NOT NULL,
|
|
390
|
-
password_hash TEXT NOT NULL,
|
|
391
|
-
role_id UUID NOT NULL REFERENCES roles(id),
|
|
392
|
-
active BOOLEAN DEFAULT true,
|
|
393
|
-
UNIQUE(tenant_id, email)
|
|
394
|
-
);
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
## Security Best Practices
|
|
398
|
-
|
|
399
|
-
1. **Short access tokens**: 15 minutes max
|
|
400
|
-
2. **Longer refresh tokens**: 7 days for UX
|
|
401
|
-
3. **Token revocation**: Store refresh token IDs in Redis
|
|
402
|
-
4. **Rate limit auth endpoints**: 5 attempts/minute
|
|
403
|
-
5. **Password hashing**: bcrypt with cost >= 12
|
|
404
|
-
6. **Secure storage**: HttpOnly cookies for refresh, memory for access
|
|
405
|
-
7. **Rotate on refresh**: Issue new refresh token on each use
|
|
406
|
-
|
|
407
|
-
## Related Skills
|
|
408
|
-
|
|
409
|
-
- `fastapi`: Python auth integration
|
|
410
|
-
- `chi-router`: Go auth middleware
|
|
411
|
-
- `redis-cache`: Token blacklist storage
|
|
412
|
-
- `zod-validation`: Token payload validation
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: notifications-concepts
|
|
3
|
-
description: >
|
|
4
|
-
Notification system concepts. Email, SMS, Push, in-app notifications, delivery tracking.
|
|
5
|
-
Trigger: notifications, email, SMS, push, FCM, APNS, in-app
|
|
6
|
-
tools:
|
|
7
|
-
- Read
|
|
8
|
-
- Write
|
|
9
|
-
- Edit
|
|
10
|
-
- Grep
|
|
11
|
-
metadata:
|
|
12
|
-
author: apigen-team
|
|
13
|
-
version: "1.0"
|
|
14
|
-
tags: [notifications, email, sms, push]
|
|
15
|
-
scope: ["**/notifications/**"]
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Notification System Concepts
|
|
19
|
-
|
|
20
|
-
## Notification Channels
|
|
21
|
-
|
|
22
|
-
### Email
|
|
23
|
-
```
|
|
24
|
-
Providers:
|
|
25
|
-
- SendGrid, Mailgun, AWS SES, Postmark
|
|
26
|
-
|
|
27
|
-
Use cases:
|
|
28
|
-
- Transactional (receipts, password reset)
|
|
29
|
-
- Marketing (newsletters, promotions)
|
|
30
|
-
- System alerts (security, billing)
|
|
31
|
-
|
|
32
|
-
Considerations:
|
|
33
|
-
- Deliverability (SPF, DKIM, DMARC)
|
|
34
|
-
- Bounce handling
|
|
35
|
-
- Unsubscribe management
|
|
36
|
-
- Template rendering
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### SMS
|
|
40
|
-
```
|
|
41
|
-
Providers:
|
|
42
|
-
- Twilio, Vonage, AWS SNS, MessageBird
|
|
43
|
-
|
|
44
|
-
Use cases:
|
|
45
|
-
- 2FA/OTP codes
|
|
46
|
-
- Order updates
|
|
47
|
-
- Appointment reminders
|
|
48
|
-
- Emergency alerts
|
|
49
|
-
|
|
50
|
-
Considerations:
|
|
51
|
-
- Character limits (160 GSM-7, 70 Unicode)
|
|
52
|
-
- Delivery receipts
|
|
53
|
-
- Opt-in/opt-out (TCPA compliance)
|
|
54
|
-
- Cost per message
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Push Notifications
|
|
58
|
-
```
|
|
59
|
-
Platforms:
|
|
60
|
-
- FCM (Firebase Cloud Messaging) - Android/Web
|
|
61
|
-
- APNS (Apple Push Notification Service) - iOS
|
|
62
|
-
- Web Push (PWA)
|
|
63
|
-
|
|
64
|
-
Use cases:
|
|
65
|
-
- Real-time updates
|
|
66
|
-
- Re-engagement
|
|
67
|
-
- Breaking news
|
|
68
|
-
- Chat messages
|
|
69
|
-
|
|
70
|
-
Considerations:
|
|
71
|
-
- Device token management
|
|
72
|
-
- Silent vs visible notifications
|
|
73
|
-
- Badge counts
|
|
74
|
-
- Action buttons
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### In-App Notifications
|
|
78
|
-
```
|
|
79
|
-
Types:
|
|
80
|
-
- Toast/snackbar (transient)
|
|
81
|
-
- Banner (persistent until dismissed)
|
|
82
|
-
- Badge (counter on icon)
|
|
83
|
-
- Feed (notification center)
|
|
84
|
-
|
|
85
|
-
Use cases:
|
|
86
|
-
- Feature announcements
|
|
87
|
-
- Activity updates
|
|
88
|
-
- Social interactions
|
|
89
|
-
- System messages
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Notification Architecture
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
┌──────────────┐
|
|
96
|
-
│ Application │
|
|
97
|
-
└──────┬───────┘
|
|
98
|
-
↓
|
|
99
|
-
┌──────────────────────────────────┐
|
|
100
|
-
│ Notification Service │
|
|
101
|
-
│ ┌────────────────────────────┐ │
|
|
102
|
-
│ │ Channel Router │ │
|
|
103
|
-
│ └────────────────────────────┘ │
|
|
104
|
-
│ ↓ ↓ ↓ │
|
|
105
|
-
│ ┌────────┐ ┌────────┐ ┌────────┐│
|
|
106
|
-
│ │ Email │ │ SMS │ │ Push ││
|
|
107
|
-
│ │Provider│ │Provider│ │Provider││
|
|
108
|
-
│ └────────┘ └────────┘ └────────┘│
|
|
109
|
-
└──────────────────────────────────┘
|
|
110
|
-
↓ ↓ ↓
|
|
111
|
-
Mailgun Twilio FCM
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Notification Data Model
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
Notification:
|
|
118
|
-
id: UUID
|
|
119
|
-
userId: string
|
|
120
|
-
type: enum (transactional, marketing, system)
|
|
121
|
-
channel: enum (email, sms, push, in_app)
|
|
122
|
-
template: string
|
|
123
|
-
data: JSON (template variables)
|
|
124
|
-
status: enum (pending, sent, delivered, failed, read)
|
|
125
|
-
scheduledAt: timestamp (null = immediate)
|
|
126
|
-
sentAt: timestamp
|
|
127
|
-
deliveredAt: timestamp
|
|
128
|
-
readAt: timestamp
|
|
129
|
-
metadata: JSON
|
|
130
|
-
|
|
131
|
-
Template:
|
|
132
|
-
id: string
|
|
133
|
-
channel: enum
|
|
134
|
-
subject: string (email only)
|
|
135
|
-
body: string (with placeholders)
|
|
136
|
-
locale: string
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## User Preferences
|
|
140
|
-
|
|
141
|
-
```json
|
|
142
|
-
{
|
|
143
|
-
"userId": "user-123",
|
|
144
|
-
"channels": {
|
|
145
|
-
"email": {
|
|
146
|
-
"enabled": true,
|
|
147
|
-
"address": "user@example.com",
|
|
148
|
-
"verified": true
|
|
149
|
-
},
|
|
150
|
-
"sms": {
|
|
151
|
-
"enabled": true,
|
|
152
|
-
"number": "+1234567890",
|
|
153
|
-
"verified": true
|
|
154
|
-
},
|
|
155
|
-
"push": {
|
|
156
|
-
"enabled": true,
|
|
157
|
-
"tokens": [
|
|
158
|
-
{"platform": "ios", "token": "xxx"},
|
|
159
|
-
{"platform": "android", "token": "yyy"}
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
"preferences": {
|
|
164
|
-
"marketing": false,
|
|
165
|
-
"orderUpdates": true,
|
|
166
|
-
"securityAlerts": true,
|
|
167
|
-
"quietHours": {
|
|
168
|
-
"enabled": true,
|
|
169
|
-
"start": "22:00",
|
|
170
|
-
"end": "08:00",
|
|
171
|
-
"timezone": "America/New_York"
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Delivery Strategies
|
|
178
|
-
|
|
179
|
-
### Priority-based Routing
|
|
180
|
-
```
|
|
181
|
-
Critical (security alerts):
|
|
182
|
-
1. Push (immediate)
|
|
183
|
-
2. SMS (fallback)
|
|
184
|
-
3. Email (always)
|
|
185
|
-
|
|
186
|
-
Transactional (order updates):
|
|
187
|
-
1. Push if available
|
|
188
|
-
2. Email always
|
|
189
|
-
|
|
190
|
-
Marketing:
|
|
191
|
-
1. Email only
|
|
192
|
-
2. Respect preferences
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Batching
|
|
196
|
-
```
|
|
197
|
-
Individual: Send immediately
|
|
198
|
-
- Password reset
|
|
199
|
-
- OTP codes
|
|
200
|
-
|
|
201
|
-
Batched: Aggregate and send
|
|
202
|
-
- Social notifications ("5 people liked your post")
|
|
203
|
-
- Activity digests
|
|
204
|
-
|
|
205
|
-
Scheduled: Send at optimal time
|
|
206
|
-
- Marketing campaigns
|
|
207
|
-
- Weekly summaries
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
## Tracking & Analytics
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
Metrics to track:
|
|
214
|
-
- Send rate (attempted)
|
|
215
|
-
- Delivery rate (confirmed)
|
|
216
|
-
- Open rate (email/push)
|
|
217
|
-
- Click rate (links)
|
|
218
|
-
- Bounce rate (email)
|
|
219
|
-
- Unsubscribe rate
|
|
220
|
-
- Opt-out rate
|
|
221
|
-
|
|
222
|
-
Events:
|
|
223
|
-
- notification.created
|
|
224
|
-
- notification.sent
|
|
225
|
-
- notification.delivered
|
|
226
|
-
- notification.opened
|
|
227
|
-
- notification.clicked
|
|
228
|
-
- notification.bounced
|
|
229
|
-
- notification.failed
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Best Practices
|
|
233
|
-
|
|
234
|
-
```
|
|
235
|
-
Content:
|
|
236
|
-
✅ Clear, actionable subject lines
|
|
237
|
-
✅ Personalization (name, context)
|
|
238
|
-
✅ Mobile-friendly formatting
|
|
239
|
-
✅ Unsubscribe link (email)
|
|
240
|
-
❌ Misleading subjects
|
|
241
|
-
❌ Excessive frequency
|
|
242
|
-
❌ Sending without consent
|
|
243
|
-
|
|
244
|
-
Technical:
|
|
245
|
-
✅ Idempotent sending
|
|
246
|
-
✅ Retry with backoff
|
|
247
|
-
✅ Rate limiting per user
|
|
248
|
-
✅ Template versioning
|
|
249
|
-
❌ Hardcoded content
|
|
250
|
-
❌ Synchronous sending
|
|
251
|
-
❌ Ignoring bounces
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Related Skills
|
|
255
|
-
|
|
256
|
-
- `notifications-spring`: Spring Boot notification implementation
|
|
257
|
-
- `apigen-architecture`: Overall system architecture
|
|
258
|
-
|
|
259
|
-
|