vibes-plug 2.5.0 → 2.14.1
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/.claude/rules/vibes-plug-core.md +32 -0
- package/.cursor/rules/vibes-plug-core.mdc +51 -0
- package/.cursorrules +42 -0
- package/AGENTS.md +37 -7
- package/BLUEPRINT.md +319 -217
- package/CHANGELOG.md +170 -1
- package/CLAUDE.md +70 -0
- package/LICENSE +1 -1
- package/README.md +611 -415
- package/index.js +19 -0
- package/package.json +44 -8
- package/plugin.json +24 -7
- package/scripts/check-anti-slop.js +53 -0
- package/scripts/generate_swarm_gif.py +295 -0
- package/scripts/install.js +201 -0
- package/skills/accessibility-testing-expert/SKILL.md +116 -0
- package/skills/ai-llm-integration-expert/SKILL.md +192 -178
- package/skills/ai-media-generation-expert/SKILL.md +172 -0
- package/skills/ai-prompt-engineering-expert/SKILL.md +134 -0
- package/skills/angular-expert/SKILL.md +148 -0
- package/skills/anti-slop/SKILL.md +133 -0
- package/skills/api-design-expert/SKILL.md +6 -3
- package/skills/api-gateway-proxy-expert/SKILL.md +81 -0
- package/skills/app-analyzer-optimizer/SKILL.md +6 -3
- package/skills/apple-ecosystem-expert/SKILL.md +144 -141
- package/skills/astro-framework-expert/SKILL.md +200 -0
- package/skills/async-queue-temporal-expert/SKILL.md +189 -26
- package/skills/authentication-identity-expert/SKILL.md +78 -181
- package/skills/autonomous-red-teamer/SKILL.md +172 -28
- package/skills/autonomous-tdd-debugger/SKILL.md +70 -64
- package/skills/biome-linter-formatter-expert/SKILL.md +89 -0
- package/skills/blockchain-web3-expert/SKILL.md +115 -0
- package/skills/bootstrap-to-modern/SKILL.md +9 -6
- package/skills/brainstorming/SKILL.md +60 -52
- package/skills/browser-automation-expert/SKILL.md +197 -21
- package/skills/bun-runtime-expert/SKILL.md +7 -1
- package/skills/chatbot-messaging-expert/SKILL.md +114 -0
- package/skills/ci-cd-devops-architect/SKILL.md +45 -36
- package/skills/cloud-hosting-expert/SKILL.md +7 -1
- package/skills/coderabbit/SKILL.md +7 -1
- package/skills/compliance-gdpr-privacy-expert/SKILL.md +85 -0
- package/skills/cron-scheduler-expert/SKILL.md +303 -297
- package/skills/data-pipeline-etl-expert/SKILL.md +84 -0
- package/skills/data-telemetry-expert/SKILL.md +7 -1
- package/skills/data-visualization-expert/SKILL.md +154 -0
- package/skills/database-orm-expert/SKILL.md +165 -294
- package/skills/deep-research-analyst/SKILL.md +136 -0
- package/skills/dependency-upgrade-migrator/SKILL.md +300 -294
- package/skills/design-system-architect/SKILL.md +309 -260
- package/skills/desktop-electron-expert/SKILL.md +128 -0
- package/skills/documentation-site-expert/SKILL.md +59 -0
- package/skills/doku-mcp-server/SKILL.md +7 -1
- package/skills/doku-payment-gateway/SKILL.md +7 -1
- package/skills/domain-driven-design-expert/SKILL.md +82 -0
- package/skills/e2e-testing-expert/SKILL.md +7 -1
- package/skills/ecommerce-expert/SKILL.md +87 -0
- package/skills/email-notification-expert/SKILL.md +393 -360
- package/skills/error-resilience-expert/SKILL.md +506 -479
- package/skills/event-driven-architect/SKILL.md +7 -1
- package/skills/feature-flag-analytics-expert/SKILL.md +65 -45
- package/skills/file-upload-media-expert/SKILL.md +436 -430
- package/skills/firebase-security-expert/SKILL.md +7 -1
- package/skills/form-validation-expert/SKILL.md +406 -400
- package/skills/fullstack-expert/SKILL.md +184 -202
- package/skills/gemini-agent-booster/SKILL.md +173 -135
- package/skills/geospatial-maps-expert/SKILL.md +80 -0
- package/skills/global-a11y-i18n-expert/SKILL.md +7 -1
- package/skills/glsl-shader-expert/SKILL.md +158 -69
- package/skills/go-programming-expert/SKILL.md +21 -15
- package/skills/graph-rag-knowledge-expert/SKILL.md +200 -0
- package/skills/graphql-apollo-expert/SKILL.md +113 -107
- package/skills/headless-cms-expert/SKILL.md +181 -0
- package/skills/hig/SKILL.md +7 -1
- package/skills/js-backend-expert/SKILL.md +218 -216
- package/skills/legacy-code-translator/SKILL.md +70 -64
- package/skills/local-slm-edge-ai-expert/SKILL.md +167 -0
- package/skills/logging-error-tracking-expert/SKILL.md +343 -337
- package/skills/mcp-server-architect/SKILL.md +308 -194
- package/skills/micro-frontend-architect/SKILL.md +111 -105
- package/skills/mobile-expo-expert/SKILL.md +8 -2
- package/skills/modern-css-native-expert/SKILL.md +189 -0
- package/skills/monorepo-architect/SKILL.md +7 -1
- package/skills/mpa-orchestrator/SKILL.md +20 -1
- package/skills/multi-agent-orchestration/SKILL.md +254 -234
- package/skills/multiple-entry-points/SKILL.md +37 -1
- package/skills/mvc-expert/SKILL.md +7 -1
- package/skills/n8n-automation-expert/SKILL.md +89 -0
- package/skills/nextjs-app-router-expert/SKILL.md +148 -0
- package/skills/openapi-swagger-codegen-expert/SKILL.md +67 -0
- package/skills/payment-gateway-expert/SKILL.md +85 -1
- package/skills/pdf-document-generation-expert/SKILL.md +91 -0
- package/skills/performance-web-vitals/SKILL.md +7 -1
- package/skills/post-quantum-crypto-migrator/SKILL.md +57 -57
- package/skills/prd-architect/SKILL.md +182 -201
- package/skills/proactive-background-watcher/SKILL.md +67 -61
- package/skills/production-ready-hardener/SKILL.md +459 -456
- package/skills/pwa-offline-first-expert/SKILL.md +226 -0
- package/skills/pydantic-ai-expert/SKILL.md +161 -0
- package/skills/python-programming-expert/SKILL.md +407 -401
- package/skills/rate-limit-abuse-prevention/SKILL.md +376 -370
- package/skills/realtime-collaboration-expert/SKILL.md +55 -1
- package/skills/rich-text-editor-expert/SKILL.md +177 -0
- package/skills/rust-programming-expert/SKILL.md +7 -1
- package/skills/saas-architect/SKILL.md +154 -0
- package/skills/saas-billing/SKILL.md +7 -1
- package/skills/saas-multi-tenant/SKILL.md +7 -1
- package/skills/scalability-clean-code/SKILL.md +7 -1
- package/skills/search-engine-expert/SKILL.md +89 -0
- package/skills/secure-fuzz-testing/SKILL.md +7 -1
- package/skills/self-healing-cloud-orchestrator/SKILL.md +57 -57
- package/skills/senior-frontend/SKILL.md +139 -161
- package/skills/senior-frontend/scripts/frontend_scaffolder.py +1 -1
- package/skills/seo/SKILL.md +41 -17
- package/skills/session-memory-manager/SKILL.md +128 -0
- package/skills/solidjs-expert/SKILL.md +80 -0
- package/skills/spa-orchestrator/SKILL.md +20 -1
- package/skills/sse-websocket-streaming-expert/SKILL.md +93 -0
- package/skills/state-management-expert/SKILL.md +7 -1
- package/skills/supabase-security-expert/SKILL.md +7 -1
- package/skills/svelte-sveltekit-expert/SKILL.md +91 -0
- package/skills/svg-animation-motion-expert/SKILL.md +115 -0
- package/skills/synthetic-data-finetuning-expert/SKILL.md +155 -0
- package/skills/tailwind-expert/SKILL.md +88 -136
- package/skills/tanstack-query-expert/SKILL.md +7 -1
- package/skills/tauri-expert/SKILL.md +7 -1
- package/skills/typescript-expert/SKILL.md +12 -6
- package/skills/ui-ux-pro-max/SKILL.md +25 -3
- package/skills/vector-db-rag-expert/SKILL.md +175 -19
- package/skills/vercel-ai-sdk-expert/SKILL.md +181 -0
- package/skills/visual-qa-vision-agent/SKILL.md +70 -64
- package/skills/voice-ai-realtime-agent/SKILL.md +242 -0
- package/skills/vue-frontend-expert/SKILL.md +131 -125
- package/skills/wasm-edge-computing-expert/SKILL.md +97 -0
- package/skills/web-3d-graphics-expert/SKILL.md +262 -80
- package/skills/web-game-engine-expert/SKILL.md +281 -48
- package/skills/web-scraper/SKILL.md +157 -207
- package/skills/website-design-cloner/SKILL.md +179 -173
- package/skills/webxr-ar-vr-expert/SKILL.md +108 -63
- package/skills/wordpress-headless-expert/SKILL.md +144 -0
- package/skills/zero-to-prod-orchestrator/SKILL.md +65 -38
- package/skills/zero-trust-secret-vault/SKILL.md +87 -39
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/workflows/publish.yml +0 -20
- package/CONTRIBUTING.md +0 -199
- package/SECURITY.md +0 -21
- package/banner.png +0 -0
- package/skills/ai-cost-token-optimizer/SKILL.md +0 -52
- package/skills/asisten_ramah/SKILL.md +0 -41
- package/skills/auto-doc-updater/SKILL.md +0 -214
- package/skills/autonomous-chaos-monkey/SKILL.md +0 -63
- package/skills/autonomous-swarm-director/SKILL.md +0 -69
- package/skills/edge-serverless-db-expert/SKILL.md +0 -43
- package/skills/hyper-context-synthesizer/SKILL.md +0 -55
- package/skills/llm-cost-arbitrage-router/SKILL.md +0 -59
- package/skills/mcp-client-orchestrator/SKILL.md +0 -70
- package/skills/mobile-push-notification-expert/SKILL.md +0 -51
- package/skills/monday-design-aesthetic/SKILL.md +0 -67
- package/skills/project-context-mapper/SKILL.md +0 -79
- package/skills/saas-mvp-launcher/SKILL.md +0 -241
- package/skills/saas-transformer/SKILL.md +0 -489
- package/skills/saas-transformer/references/billing_integration_guide.md +0 -401
- package/skills/self-evolving-memory-graph/SKILL.md +0 -75
- package/skills/senior-fullstack/SKILL.md +0 -167
- package/skills/senior-fullstack/references/architecture_patterns.md +0 -160
- package/skills/senior-fullstack/references/development_workflows.md +0 -222
- package/skills/senior-fullstack/references/tech_stack_guide.md +0 -190
- package/skills/senior-fullstack/scripts/code_quality_analyzer.py +0 -114
- package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +0 -114
- package/skills/senior-fullstack/scripts/project_scaffolder.py +0 -114
- package/skills/seo-aeo-landing-page-writer/SKILL.md +0 -97
- package/skills/seo-geo/SKILL.md +0 -188
- package/skills/session-context-loader/SKILL.md +0 -77
- package/skills/session-handoff-resume/SKILL.md +0 -158
- package/skills/skill_baru/SKILL.md +0 -172
- package/skills/supabase-migration/SKILL.md +0 -45
- package/skills/token-saver/SKILL.md +0 -119
- package/skills/ui-components-expert/SKILL.md +0 -280
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/skills/ui_ux_expert/SKILL.md +0 -125
- package/skills/vibe-code-gardener/SKILL.md +0 -181
- package/vibes-swarm-demo.gif +0 -0
- /package/skills/{saas-transformer → saas-architect}/references/feature_gating_patterns.md +0 -0
- /package/skills/{saas-transformer → saas-architect}/references/saas_transformation_checklist.md +0 -0
- /package/skills/{saas-transformer → saas-architect}/scripts/saas_transformation_scanner.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: async-queue-temporal-expert
|
|
3
|
-
description: "
|
|
4
|
-
author:
|
|
3
|
+
description: "Unified expert guide for async job queues & durable workflows: BullMQ v5 (Redis queues), Trigger.dev v3 (serverless tasks), Inngest, and Temporal.io (distributed sagas) / Panduan ahli terpadu untuk antrean job asinkron & workflow tahan-gagal: BullMQ v5, Trigger.dev v3, Inngest, dan Temporal.io."
|
|
4
|
+
author: "Roedy Rustam"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Async Queue & Durable Workflow Expert
|
|
7
|
+
# Async Queue & Durable Workflow Expert (2026 Unified Edition)
|
|
8
8
|
|
|
9
9
|
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
10
|
|
|
@@ -14,23 +14,112 @@ author: vibes-plug-swarm
|
|
|
14
14
|
## English
|
|
15
15
|
|
|
16
16
|
### Purpose & Overview
|
|
17
|
-
|
|
17
|
+
Unified production-grade guide for background execution pipelines, async job queues, and distributed state machines. Covers the full spectrum from simple Redis-backed task queues to complex multi-service sagas with compensating rollbacks.
|
|
18
18
|
|
|
19
|
-
###
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
### 3-Tier Execution Model
|
|
20
|
+
| Tier | Engine | Best For |
|
|
21
|
+
|------|--------|----------|
|
|
22
|
+
| **Tier 1: Redis Task Queues** | BullMQ v5 | High-throughput worker jobs, priority queues, rate limiting, DLQ |
|
|
23
|
+
| **Tier 2: Serverless Durable Tasks** | Trigger.dev v3 / Inngest | Step-checkpointed tasks, automatic resume across crashes, zero infra |
|
|
24
|
+
| **Tier 3: Distributed Sagas** | Temporal.io | Multi-service orchestration, compensating rollbacks, long-running workflows |
|
|
25
|
+
|
|
26
|
+
### Core Capabilities
|
|
27
|
+
1. **Idempotency & Deduplication**: Deterministic `jobId` keys prevent double billing or duplicate emails.
|
|
28
|
+
2. **Dead Letter Queues (DLQ)**: Auto-relocate permanently failing jobs for audit and alerting.
|
|
29
|
+
3. **Exponential Backoff with Jitter**: Prevents thundering herds on upstream services.
|
|
30
|
+
4. **Tenant Priority Queues**: VIP/enterprise tiers get lower BullMQ priority numbers (higher throughput).
|
|
31
|
+
5. **Durable State Machines**: Workflows survive restarts, deployments, and network partitions.
|
|
32
|
+
6. **Saga Compensations**: Multi-step transactions with automated reverse-order rollbacks.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### Tier 1: BullMQ v5 — Redis Task Queues (TypeScript)
|
|
23
37
|
|
|
24
38
|
```typescript
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
import { Queue, Worker, Job } from 'bullmq';
|
|
40
|
+
import Redis from 'ioredis';
|
|
41
|
+
|
|
42
|
+
const redisConnection = new Redis(process.env.REDIS_URL!, {
|
|
43
|
+
maxRetriesPerRequest: null, // Required by BullMQ
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export interface NotificationPayload {
|
|
47
|
+
tenantId: string;
|
|
48
|
+
userId: string;
|
|
49
|
+
type: 'email' | 'webhook';
|
|
50
|
+
payload: Record<string, unknown>;
|
|
51
|
+
idempotencyKey: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Main Queue
|
|
55
|
+
export const notificationQueue = new Queue<NotificationPayload>('notifications', {
|
|
56
|
+
connection: redisConnection,
|
|
57
|
+
defaultJobOptions: {
|
|
58
|
+
attempts: 5,
|
|
59
|
+
backoff: { type: 'exponential', delay: 1500 },
|
|
60
|
+
removeOnComplete: { age: 86400, count: 5000 },
|
|
61
|
+
removeOnFail: false, // Preserved for DLQ audit
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Dead Letter Queue
|
|
66
|
+
export const notificationDLQ = new Queue('notifications-dlq', {
|
|
67
|
+
connection: redisConnection,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Enqueue with deduplication & priority
|
|
71
|
+
export async function enqueueNotification(data: NotificationPayload, isVip = false) {
|
|
72
|
+
return await notificationQueue.add('send_notification', data, {
|
|
73
|
+
jobId: `notif_${data.idempotencyKey}`, // Deterministic dedup key
|
|
74
|
+
priority: isVip ? 1 : 10,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Worker with concurrency & rate limiting
|
|
79
|
+
export const notificationWorker = new Worker<NotificationPayload>(
|
|
80
|
+
'notifications',
|
|
81
|
+
async (job: Job<NotificationPayload>) => {
|
|
82
|
+
if (job.data.type === 'email') await deliverEmail(job.data);
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
connection: redisConnection,
|
|
86
|
+
concurrency: 20,
|
|
87
|
+
limiter: { max: 100, duration: 1000 },
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// DLQ forwarding on retry exhaustion
|
|
92
|
+
notificationWorker.on('failed', async (job, error) => {
|
|
93
|
+
if (job && job.attemptsMade >= (job.opts.attempts || 5)) {
|
|
94
|
+
await notificationDLQ.add('failed_notification', {
|
|
95
|
+
originalJobId: job.id, failedReason: error.message,
|
|
96
|
+
data: job.data, exhaustedAt: new Date().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Tier 2: Trigger.dev v3 — Serverless Durable Tasks
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { task } from '@trigger.dev/sdk/v3';
|
|
108
|
+
|
|
109
|
+
export const generateReport = task({
|
|
110
|
+
id: 'generate-enterprise-report',
|
|
111
|
+
retry: { maxAttempts: 4, minTimeoutInMs: 2000, factor: 2, randomize: true },
|
|
112
|
+
run: async (payload: { tenantId: string; month: string }, { ctx }) => {
|
|
113
|
+
// Each step is a durable checkpoint — survives crashes
|
|
114
|
+
const data = await ctx.run('fetch-telemetry', async () => {
|
|
115
|
+
return await fetchTelemetryFromWarehouse(payload.tenantId, payload.month);
|
|
116
|
+
});
|
|
117
|
+
const pdfUrl = await ctx.run('render-pdf', async () => {
|
|
118
|
+
return await generateReportPdf(data);
|
|
119
|
+
});
|
|
120
|
+
await ctx.run('dispatch-webhook', async () => {
|
|
121
|
+
return await sendWebhookNotification(payload.tenantId, pdfUrl);
|
|
122
|
+
});
|
|
34
123
|
return { success: true, pdfUrl };
|
|
35
124
|
},
|
|
36
125
|
});
|
|
@@ -38,17 +127,91 @@ export const generateSaaSReport = task({
|
|
|
38
127
|
|
|
39
128
|
---
|
|
40
129
|
|
|
130
|
+
### Tier 3: Temporal.io — Distributed Saga with Compensations
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { proxyActivities, ApplicationFailure } from '@temporalio/workflow';
|
|
134
|
+
import type * as activities from './activities';
|
|
135
|
+
|
|
136
|
+
const { chargeCustomer, provisionLicense, sendWelcomeEmail, refundCustomer, revokeLicense } =
|
|
137
|
+
proxyActivities<typeof activities>({
|
|
138
|
+
startToCloseTimeout: '1 minute',
|
|
139
|
+
retry: {
|
|
140
|
+
initialInterval: '1s', backoffCoefficient: 2, maximumAttempts: 5,
|
|
141
|
+
nonRetryableErrorTypes: ['InvalidCardError', 'AccountSuspendedError'],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export async function subscriptionSagaWorkflow(input: {
|
|
146
|
+
customerId: string; planId: string; amountCents: number;
|
|
147
|
+
}) {
|
|
148
|
+
const compensations: Array<() => Promise<void>> = [];
|
|
149
|
+
try {
|
|
150
|
+
const charge = await chargeCustomer(input.customerId, input.amountCents);
|
|
151
|
+
compensations.unshift(() => refundCustomer(charge.chargeId));
|
|
152
|
+
|
|
153
|
+
const license = await provisionLicense(input.customerId, input.planId);
|
|
154
|
+
compensations.unshift(() => revokeLicense(license.licenseId));
|
|
155
|
+
|
|
156
|
+
await sendWelcomeEmail(input.customerId, license.licenseKey);
|
|
157
|
+
return { status: 'COMPLETED' };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
for (const compensate of compensations) {
|
|
160
|
+
try { await compensate(); } catch (e) { console.error('Compensation failed:', e); }
|
|
161
|
+
}
|
|
162
|
+
throw ApplicationFailure.create({
|
|
163
|
+
message: `Saga rolled back: ${(error as Error).message}`, nonRetryable: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> **Temporal Determinism Rule**: Never use `Math.random()`, `Date.now()`, or direct DB calls inside workflow files. Run them inside activities.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### Implementation Checklist
|
|
174
|
+
- [ ] Configure `maxRetriesPerRequest: null` on ioredis for BullMQ v5.
|
|
175
|
+
- [ ] Use deterministic `jobId` from business logic (`order_${orderId}`) for deduplication.
|
|
176
|
+
- [ ] Forward permanently dead jobs to DLQ via `worker.on('failed')` listener.
|
|
177
|
+
- [ ] Implement rate limiting via worker `limiter` to protect third-party APIs.
|
|
178
|
+
- [ ] Enforce deterministic code inside Temporal workflows (activities for side-effects).
|
|
179
|
+
- [ ] Store large payloads in S3/R2; pass only IDs through queues.
|
|
180
|
+
- [ ] Implement Saga rollback handlers for multi-step distributed payments.
|
|
181
|
+
|
|
182
|
+
## Orchestration & Integration
|
|
183
|
+
- Integrates with: `js-backend-expert`, `cron-scheduler-expert`, `error-resilience-expert`, `saas-billing`, `doku-payment-gateway`, `data-telemetry-expert`.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
41
187
|
<a name="bahasa-indonesia"></a>
|
|
42
188
|
## Bahasa Indonesia
|
|
43
189
|
|
|
44
|
-
###
|
|
45
|
-
Panduan tingkat produksi untuk
|
|
190
|
+
### Tujuan & Gambaran Umum
|
|
191
|
+
Panduan terpadu tingkat produksi untuk pipeline eksekusi latar belakang, antrean job asinkron, dan state machine terdistribusi. Mencakup spektrum penuh dari antrean Redis sederhana hingga saga multi-layanan dengan rollback kompensasi.
|
|
46
192
|
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
193
|
+
### Model Eksekusi 3-Tier
|
|
194
|
+
| Tier | Engine | Cocok Untuk |
|
|
195
|
+
|------|--------|-------------|
|
|
196
|
+
| **Tier 1: Antrean Redis** | BullMQ v5 | Job worker throughput tinggi, prioritas, rate limiting, DLQ |
|
|
197
|
+
| **Tier 2: Task Serverless** | Trigger.dev v3 / Inngest | Task dengan checkpoint, resume otomatis, tanpa infra |
|
|
198
|
+
| **Tier 3: Saga Terdistribusi** | Temporal.io | Orkestrasi multi-layanan, rollback kompensasi, workflow jangka panjang |
|
|
52
199
|
|
|
53
|
-
|
|
54
|
-
|
|
200
|
+
### Kemampuan Utama
|
|
201
|
+
1. **Idempotensi & Deduplikasi**: Kunci `jobId` deterministik mencegah duplikasi penagihan atau email.
|
|
202
|
+
2. **Dead Letter Queue (DLQ)**: Pemindahan otomatis job gagal total untuk audit.
|
|
203
|
+
3. **Backoff Eksponensial + Jitter**: Mencegah thundering herd pada server hilir.
|
|
204
|
+
4. **Prioritas Tenant**: Tier VIP/enterprise mendapat prioritas lebih tinggi (angka lebih kecil di BullMQ).
|
|
205
|
+
5. **State Machine Tahan-Gagal**: Workflow bertahan saat restart, deployment, dan partisi jaringan.
|
|
206
|
+
6. **Kompensasi Saga**: Transaksi multi-langkah dengan rollback otomatis urutan mundur.
|
|
207
|
+
|
|
208
|
+
### Checklist Implementasi
|
|
209
|
+
- [ ] Atur `maxRetriesPerRequest: null` pada ioredis untuk BullMQ v5.
|
|
210
|
+
- [ ] Gunakan `jobId` deterministik dari ID bisnis (`invoice_${invoiceId}`) untuk deduplikasi.
|
|
211
|
+
- [ ] Pasang listener `worker.on('failed')` untuk forward job gagal ke DLQ.
|
|
212
|
+
- [ ] Terapkan rate limiter pada worker untuk stabilitas API eksternal.
|
|
213
|
+
- [ ] Pastikan kode Temporal selalu deterministik (side-effect hanya di activities).
|
|
214
|
+
- [ ] Simpan file besar di S3/R2; kirim hanya referensi ID melalui queue.
|
|
215
|
+
|
|
216
|
+
## Integrasi Orkestrasi
|
|
217
|
+
- Terintegrasi dengan: `js-backend-expert`, `cron-scheduler-expert`, `error-resilience-expert`, `saas-billing`, `doku-payment-gateway`, `data-telemetry-expert`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: authentication-identity-expert
|
|
3
3
|
description: "Expert guide for implementing secure authentication, authorization (RBAC/ABAC), OAuth2, and identity management (Clerk, Auth.js, Supabase Auth) / Panduan ahli untuk autentikasi dan otorisasi."
|
|
4
|
-
author:
|
|
4
|
+
author: "Roedy Rustam"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Authentication & Identity Expert (2026 Edition)
|
|
@@ -14,88 +14,73 @@ author: vibes-plug-swarm
|
|
|
14
14
|
## English
|
|
15
15
|
|
|
16
16
|
### Description
|
|
17
|
-
Production-grade guide for implementing secure authentication, authorization, and identity management in modern web and mobile applications. Covers **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, JWT patterns, OAuth 2.1, PKCE flows, RBAC/ABAC, passkeys (WebAuthn), and MFA
|
|
17
|
+
Production-grade guide for implementing secure authentication, authorization, and identity management in modern web and mobile applications. Covers **Clerk**, **Supabase Auth**, **Auth.js v5**, **Better Auth**, JWT patterns, OAuth 2.1, PKCE flows, RBAC/ABAC, passkeys (WebAuthn), and MFA implementations for React 19 / Next.js 15 stacks.
|
|
18
18
|
|
|
19
19
|
### Trigger Conditions
|
|
20
|
-
- Working on login/signup flows, session management, or OAuth
|
|
21
|
-
- Integrating
|
|
22
|
-
- Implementing
|
|
23
|
-
- Setting up
|
|
20
|
+
- Working on login/signup flows, session management, or OAuth integrations.
|
|
21
|
+
- Integrating Clerk, Supabase Auth, Auth.js v5, Better Auth, or Auth0.
|
|
22
|
+
- Implementing RBAC (Role-Based Access Control) or ABAC.
|
|
23
|
+
- Setting up WebAuthn/Passkeys, MFA/2FA, or SSO.
|
|
24
24
|
- Securing Next.js App Router routes with middleware-based auth guards.
|
|
25
|
-
- Implementing **OAuth 2.1** flows with PKCE for SPAs or mobile apps.
|
|
26
25
|
|
|
27
26
|
---
|
|
28
27
|
|
|
29
|
-
### Identity Provider Selection Guide
|
|
28
|
+
### Identity Provider Selection Guide
|
|
30
29
|
|
|
31
|
-
| Provider | Best For | Key Strength |
|
|
32
|
-
|
|
33
|
-
| **Clerk** | SaaS
|
|
34
|
-
| **Supabase Auth** | Supabase
|
|
35
|
-
| **Auth.js v5** |
|
|
36
|
-
| **Better Auth** |
|
|
37
|
-
| **Auth0** | Enterprise, compliance | Enterprise SSO, compliance (SOC2, HIPAA) |
|
|
38
|
-
|
|
39
|
-
**Recommendation**: Use **Clerk** for most new SaaS apps (best DX). Use **Supabase Auth** if you're already using Supabase. Use **Better Auth** for full control without vendor lock-in.
|
|
30
|
+
| Provider | Best For | Key Strength | Pricing Model |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| **Clerk** | New SaaS products | Best developer DX, prebuilt UI components, orgs/teams out-of-the-box | Per MAU (free up to 10k) |
|
|
33
|
+
| **Supabase Auth** | Existing Supabase apps | Native RLS integration, multi-provider OAuth, generous free tier | Included with Supabase |
|
|
34
|
+
| **Auth.js v5** | Self-hosted control | Full data ownership, zero vendor lock-in, Edge-compatible | 100% Free / OSS |
|
|
35
|
+
| **Better Auth** | Modern TS-first apps | Comprehensive TypeScript plugin ecosystem, 2FA, passkeys | 100% Free / OSS |
|
|
40
36
|
|
|
41
37
|
---
|
|
42
38
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
#### 1. Never Roll Your Own Crypto
|
|
46
|
-
- Use `argon2id` (preferred) or `bcrypt` for password hashing — **never MD5, SHA1, or plain SHA256**.
|
|
47
|
-
- Use battle-tested libraries: `@node-rs/argon2`, `bcryptjs`.
|
|
48
|
-
- Minimum: bcrypt cost factor ≥12, argon2id memory ≥64MB.
|
|
39
|
+
### Technical Guidelines & Recipes
|
|
49
40
|
|
|
50
|
-
####
|
|
41
|
+
#### 1. Password Hashing Standards
|
|
51
42
|
```typescript
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
import argon2 from 'argon2';
|
|
44
|
+
|
|
45
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
46
|
+
return argon2.hash(password, {
|
|
47
|
+
type: argon2.argon2id,
|
|
48
|
+
memoryCost: 65536, // 64 MB
|
|
49
|
+
timeCost: 3,
|
|
50
|
+
parallelism: 4,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
```
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
#### 2. Short-Lived JWT + Refresh Token Rotation
|
|
56
|
+
```typescript
|
|
57
|
+
// Access token: 15 minutes, in memory
|
|
58
|
+
// Refresh token: 7-30 days, HttpOnly, Secure, SameSite=Strict cookie
|
|
66
59
|
```
|
|
67
60
|
|
|
68
|
-
#### 3. OAuth 2.1 + PKCE
|
|
61
|
+
#### 3. OAuth 2.1 + PKCE
|
|
69
62
|
```typescript
|
|
70
|
-
// PKCE flow — required for public clients in OAuth 2.1
|
|
71
63
|
import { generateCodeVerifier, generateCodeChallenge } from 'oslo/oauth2';
|
|
72
64
|
|
|
73
|
-
const codeVerifier = generateCodeVerifier();
|
|
74
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
75
|
-
|
|
76
|
-
// Store codeVerifier in sessionStorage (NOT localStorage) during redirect
|
|
65
|
+
const codeVerifier = generateCodeVerifier();
|
|
66
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
77
67
|
sessionStorage.setItem('pkce_verifier', codeVerifier);
|
|
78
68
|
|
|
79
|
-
// Auth URL includes challenge
|
|
80
69
|
const authUrl = new URL('https://provider.com/oauth/authorize');
|
|
81
70
|
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
82
71
|
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
83
|
-
authUrl.searchParams.set('state', cryptoRandomState);
|
|
72
|
+
authUrl.searchParams.set('state', cryptoRandomState);
|
|
84
73
|
```
|
|
85
74
|
|
|
86
75
|
#### 4. Clerk — Next.js 15 Integration
|
|
87
76
|
```typescript
|
|
88
|
-
// middleware.ts
|
|
77
|
+
// middleware.ts
|
|
89
78
|
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
|
|
90
79
|
|
|
91
|
-
const isPublicRoute = createRouteMatcher([
|
|
92
|
-
'/', '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)',
|
|
93
|
-
]);
|
|
80
|
+
const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)']);
|
|
94
81
|
|
|
95
82
|
export default clerkMiddleware(async (auth, req) => {
|
|
96
|
-
if (!isPublicRoute(req))
|
|
97
|
-
await auth.protect(); // Redirects to sign-in if unauthenticated
|
|
98
|
-
}
|
|
83
|
+
if (!isPublicRoute(req)) await auth.protect();
|
|
99
84
|
});
|
|
100
85
|
|
|
101
86
|
export const config = {
|
|
@@ -103,47 +88,11 @@ export const config = {
|
|
|
103
88
|
};
|
|
104
89
|
```
|
|
105
90
|
|
|
106
|
-
```typescript
|
|
107
|
-
// Server Component — get user on the server
|
|
108
|
-
import { auth, currentUser } from '@clerk/nextjs/server';
|
|
109
|
-
|
|
110
|
-
export default async function DashboardPage() {
|
|
111
|
-
const { userId, orgId, orgRole } = await auth();
|
|
112
|
-
if (!userId) redirect('/sign-in');
|
|
113
|
-
|
|
114
|
-
const user = await currentUser();
|
|
115
|
-
return <div>Welcome, {user?.firstName}!</div>;
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
91
|
#### 5. Supabase Auth — RLS Integration
|
|
120
|
-
|
|
121
|
-
// Create Supabase client with user session
|
|
122
|
-
import { createServerClient } from '@supabase/ssr';
|
|
123
|
-
|
|
124
|
-
export async function createSupabaseServerClient(cookieStore: ReadonlyRequestCookies) {
|
|
125
|
-
return createServerClient(
|
|
126
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
127
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
128
|
-
{
|
|
129
|
-
cookies: {
|
|
130
|
-
getAll: () => cookieStore.getAll(),
|
|
131
|
-
setAll: (cookies) => cookies.forEach(({ name, value, options }) =>
|
|
132
|
-
cookieStore.set(name, value, options)
|
|
133
|
-
),
|
|
134
|
-
},
|
|
135
|
-
}
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// RLS in PostgreSQL — users can only access their own rows
|
|
140
|
-
-- CREATE POLICY "Users can only see own data" ON profiles
|
|
141
|
-
-- FOR ALL USING (auth.uid() = user_id);
|
|
142
|
-
```
|
|
92
|
+
> For detailed Supabase Auth SSR setup, PKCE session cookie handling, and PostgreSQL Row-Level Security (RLS) policies, delegate directly to `supabase-security-expert`.
|
|
143
93
|
|
|
144
94
|
#### 6. Auth.js v5 (Next.js App Router)
|
|
145
95
|
```typescript
|
|
146
|
-
// auth.ts
|
|
147
96
|
import NextAuth from 'next-auth';
|
|
148
97
|
import GitHub from 'next-auth/providers/github';
|
|
149
98
|
import { DrizzleAdapter } from '@auth/drizzle-adapter';
|
|
@@ -151,80 +100,53 @@ import { db } from '@/db';
|
|
|
151
100
|
|
|
152
101
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
153
102
|
adapter: DrizzleAdapter(db),
|
|
154
|
-
providers: [
|
|
155
|
-
|
|
156
|
-
// Add more providers...
|
|
157
|
-
],
|
|
158
|
-
session: { strategy: 'database' }, // or 'jwt'
|
|
159
|
-
callbacks: {
|
|
160
|
-
authorized({ auth, request: { nextUrl } }) {
|
|
161
|
-
const isLoggedIn = !!auth?.user;
|
|
162
|
-
const isProtected = nextUrl.pathname.startsWith('/dashboard');
|
|
163
|
-
if (isProtected && !isLoggedIn) return false;
|
|
164
|
-
return true;
|
|
165
|
-
},
|
|
166
|
-
},
|
|
103
|
+
providers: [GitHub],
|
|
104
|
+
session: { strategy: 'database' },
|
|
167
105
|
});
|
|
168
106
|
```
|
|
169
107
|
|
|
170
108
|
#### 7. RBAC — Role-Based Access Control (Server-Side)
|
|
171
109
|
```typescript
|
|
172
|
-
// ✅ Always enforce roles server-side — never rely on client UI alone
|
|
173
110
|
type Role = 'viewer' | 'editor' | 'admin' | 'super_admin';
|
|
174
111
|
|
|
175
112
|
const permissions: Record<Role, string[]> = {
|
|
176
113
|
viewer: ['read:posts'],
|
|
177
114
|
editor: ['read:posts', 'write:posts', 'delete:own_posts'],
|
|
178
115
|
admin: ['read:posts', 'write:posts', 'delete:any_post', 'manage:users'],
|
|
179
|
-
super_admin: ['*'],
|
|
116
|
+
super_admin: ['*'],
|
|
180
117
|
};
|
|
181
118
|
|
|
182
|
-
function hasPermission(userRole: Role, required: string): boolean {
|
|
183
|
-
const allowed = permissions[userRole];
|
|
119
|
+
export function hasPermission(userRole: Role, required: string): boolean {
|
|
120
|
+
const allowed = permissions[userRole] || [];
|
|
184
121
|
return allowed.includes('*') || allowed.includes(required);
|
|
185
122
|
}
|
|
186
|
-
|
|
187
|
-
// In Server Action / API Route
|
|
188
|
-
const { userId } = await auth();
|
|
189
|
-
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
|
|
190
|
-
|
|
191
|
-
if (!hasPermission(user.role, 'delete:any_post')) {
|
|
192
|
-
throw new Error('Forbidden');
|
|
193
|
-
}
|
|
194
123
|
```
|
|
195
124
|
|
|
196
125
|
#### 8. WebAuthn / Passkeys (2026 Standard)
|
|
197
126
|
```typescript
|
|
198
|
-
// Using SimpleWebAuthn library
|
|
199
127
|
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
|
|
200
128
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
residentKey: 'preferred',
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Store challenge in session, send options to client
|
|
214
|
-
await redis.setex(`webauthn:${userId}`, 60, JSON.stringify(options.challenge));
|
|
129
|
+
export async function getPasskeyRegistrationOptions(userId: string, userEmail: string) {
|
|
130
|
+
return generateRegistrationOptions({
|
|
131
|
+
rpName: 'My App',
|
|
132
|
+
rpID: 'myapp.com',
|
|
133
|
+
userID: new TextEncoder().encode(userId),
|
|
134
|
+
userName: userEmail,
|
|
135
|
+
attestationType: 'none',
|
|
136
|
+
authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
215
139
|
```
|
|
216
140
|
|
|
217
|
-
### Security Checklist
|
|
218
|
-
- [ ] Passwords hashed with argon2id
|
|
219
|
-
- [ ] JWT access
|
|
220
|
-
- [ ] OAuth state parameter validated on callback (CSRF protection).
|
|
141
|
+
### Production Security Checklist
|
|
142
|
+
- [ ] Passwords hashed with argon2id or bcrypt (cost >= 12).
|
|
143
|
+
- [ ] JWT access token expires in <= 15 minutes; refresh token in HttpOnly cookie.
|
|
221
144
|
- [ ] PKCE enforced for all public OAuth clients.
|
|
222
|
-
- [ ]
|
|
223
|
-
- [ ]
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
- [ ] Account lockout after N failed attempts.
|
|
145
|
+
- [ ] Authorization enforced on server actions and API routes (never UI only).
|
|
146
|
+
- [ ] Rate limiting on login and signup endpoints.
|
|
147
|
+
|
|
148
|
+
## Orchestration & Integration
|
|
149
|
+
- Connects with `supabase-security-expert`, `saas-architect`, `js-backend-expert`, `firebase-security-expert`, and `production-ready-hardener`.
|
|
228
150
|
|
|
229
151
|
---
|
|
230
152
|
|
|
@@ -232,45 +154,20 @@ await redis.setex(`webauthn:${userId}`, 60, JSON.stringify(options.challenge));
|
|
|
232
154
|
## Bahasa Indonesia
|
|
233
155
|
|
|
234
156
|
### Deskripsi
|
|
235
|
-
Panduan tingkat produksi untuk
|
|
236
|
-
|
|
237
|
-
###
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
#### 2. Pemilihan Identity Provider
|
|
253
|
-
- **Clerk**: Terbaik untuk SaaS baru — DX terbaik, komponen UI siap pakai, manajemen organisasi.
|
|
254
|
-
- **Supabase Auth**: Ideal jika sudah pakai Supabase — integrasi langsung dengan RLS.
|
|
255
|
-
- **Auth.js v5**: Self-hosted, fleksibel, edge-compatible untuk Next.js.
|
|
256
|
-
- **Better Auth**: TypeScript-first, kontrol penuh tanpa vendor lock-in.
|
|
257
|
-
|
|
258
|
-
#### 3. RBAC di Sisi Server
|
|
259
|
-
Selalu terapkan pengecekan role di server (Server Action, API route, middleware) — **jangan hanya di UI**. Gunakan tabel permission yang memetakan setiap role ke daftar aksi yang diizinkan.
|
|
260
|
-
|
|
261
|
-
#### 4. WebAuthn / Passkeys
|
|
262
|
-
Gunakan library `@simplewebauthn/server` untuk registrasi dan verifikasi passkey. Simpan challenge di Redis dengan TTL pendek (60 detik) untuk mencegah replay attack.
|
|
263
|
-
|
|
264
|
-
#### 5. Checklist Keamanan
|
|
265
|
-
- Password di-hash dengan argon2id atau bcrypt.
|
|
266
|
-
- JWT access token expire ≤15 menit; refresh di HttpOnly cookie.
|
|
267
|
-
- State OAuth divalidasi saat callback.
|
|
268
|
-
- PKCE diterapkan untuk semua klien publik OAuth.
|
|
269
|
-
- Pengecekan otorisasi dilakukan di server, bukan hanya UI.
|
|
270
|
-
- MFA ditawarkan untuk akun privileged.
|
|
271
|
-
- Rate limiting di endpoint login/signup.
|
|
272
|
-
- Rotasi refresh token setiap kali digunakan.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
## Orchestration & Integration
|
|
276
|
-
- Connects to other backend skills as part of the orchestration flow.
|
|
157
|
+
Panduan tingkat produksi untuk autentikasi, otorisasi, dan manajemen identitas modern (Clerk, Supabase Auth, Auth.js v5, Better Auth, OAuth 2.1 PKCE, RBAC, dan Passkeys).
|
|
158
|
+
|
|
159
|
+
### Rekomendasi Identity Provider
|
|
160
|
+
- **Clerk**: Terbaik untuk SaaS baru (UI bawaan, manajemen tim/organisasi).
|
|
161
|
+
- **Supabase Auth**: Terbaik jika memakai database Supabase (integrasi native RLS).
|
|
162
|
+
- **Auth.js v5**: Self-hosted, kontrol penuh tanpa vendor lock-in.
|
|
163
|
+
- **Better Auth**: TypeScript-first, fitur 2FA dan passkey bawaan.
|
|
164
|
+
|
|
165
|
+
### Poin Kunci Keamanan
|
|
166
|
+
1. **Password Hashing**: Gunakan `argon2id` atau `bcrypt` (cost >= 12).
|
|
167
|
+
2. **Token Berumur Pendek**: Access token <= 15 menit, refresh token di cookie HttpOnly dengan rotasi token.
|
|
168
|
+
3. **PKCE Wajib**: Terapkan PKCE untuk semua klien publik OAuth.
|
|
169
|
+
4. **Otorisasi Server-Side**: Wajibkan verifikasi hak akses di server (bukan hanya UI).
|
|
170
|
+
5. **Supabase Auth**: Untuk setup SSR dan RLS mendalam, delegasikan ke `supabase-security-expert`.
|
|
171
|
+
|
|
172
|
+
## Integrasi Orkestrasi
|
|
173
|
+
- Terhubung dengan `supabase-security-expert`, `saas-architect`, `js-backend-expert`, `firebase-security-expert`, dan `production-ready-hardener`.
|