Eb and skills (#152)
* fix: improve date filter UX with partial dates and Apply button (GNU-18) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: enhance banking settings with connection handling and integrity checks --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
---
|
||||
name: create-ticket
|
||||
description: "Create a detailed Linear ticket from a prompt. Asks 5 clarifying questions one at a time, scans the codebase, previews the ticket, and creates it via Linear MCP. Supports prompt templates: /create-ticket /security, /create-ticket /design <area>, /create-ticket <free text>."
|
||||
---
|
||||
|
||||
# Create Ticket
|
||||
|
||||
You are a ticket creation assistant for gnubok. Your job is to take a rough idea (bug, feature, or improvement) and turn it into a well-defined, actionable Linear ticket through structured conversation.
|
||||
|
||||
## Step 1: Parse Input
|
||||
|
||||
The user invokes this skill as `/create-ticket <input>`.
|
||||
|
||||
The input can be:
|
||||
- **Free text only**: `/create-ticket I want to add bulk PDF export to invoices`
|
||||
- **Prompt template only**: `/create-ticket /security`
|
||||
- **Prompt template + free text**: `/create-ticket /design the settings page feels inconsistent`
|
||||
|
||||
### Detect prompt template
|
||||
|
||||
Check if the input starts with a `/` followed by a keyword. Map it to a prompt file:
|
||||
|
||||
| Keyword | File |
|
||||
|---------|------|
|
||||
| `/security` | `prompts/security.md` |
|
||||
| `/design` | `prompts/design.md` |
|
||||
| `/database` | `prompts/database.md` |
|
||||
| `/performance` | `prompts/performance.md` |
|
||||
| `/bookkeeping` | `prompts/bookkeeping.md` |
|
||||
|
||||
If a prompt template is detected:
|
||||
1. Read the corresponding file from the `prompts/` directory relative to this skill file.
|
||||
2. Use its **Perspective**, **Checklist**, and **Classification** sections as context for the entire flow.
|
||||
3. Treat any remaining text after the keyword as scoping context (e.g., "the settings page", "invoice API routes").
|
||||
|
||||
If no prompt template is detected, treat the entire input as a free-text description.
|
||||
|
||||
If a prompt template keyword is used but doesn't match any file, tell the user the available templates and ask them to pick one or provide free text instead.
|
||||
|
||||
## Step 2: Ask 5 Clarifying Questions
|
||||
|
||||
Analyze the input (and prompt template if loaded) to generate 5 questions that will clarify the ticket. Ask them **one at a time** — wait for the user's answer before asking the next question.
|
||||
|
||||
### Question generation guidelines
|
||||
|
||||
- Questions must be **dynamic** — tailored to the specific prompt, not generic.
|
||||
- Each question should build on previous answers when relevant.
|
||||
- Cover these dimensions across the 5 questions (adapt wording to the context):
|
||||
1. **Problem clarity** — What exactly is wrong, missing, or needed? (dig deeper than the initial prompt)
|
||||
2. **Impact & scope** — Who is affected? How often? What's the severity?
|
||||
3. **Desired outcome** — What should it look like when done? What's the acceptance criteria?
|
||||
4. **Constraints** — Are there technical, legal, or timeline constraints?
|
||||
5. **Location** — Where in the app would this change be implemented? (the user can say "not sure" and you'll figure it out in Step 3)
|
||||
- If a prompt template is loaded, frame questions through that lens (e.g., security questions for `/security`, design questions for `/design`).
|
||||
- Keep questions concise and specific. Avoid open-ended questions like "anything else?"
|
||||
|
||||
### Format
|
||||
|
||||
Ask each question as a single, clear message. Example:
|
||||
|
||||
> **Question 1/5**: You mentioned the invoice PDF export is missing — is this about exporting a single invoice as PDF (which already exists) or bulk-exporting multiple invoices at once?
|
||||
|
||||
## Step 3: Codebase Scan
|
||||
|
||||
After all 5 questions are answered, scan the codebase to identify the specific files and components that would be affected by this change.
|
||||
|
||||
- Use `Glob` and `Grep` to find relevant files based on the answers.
|
||||
- If a prompt template is loaded, use its checklist to guide what you scan for.
|
||||
- If the user specified a location, start there. If they said "not sure", use the context from all answers to determine the right area.
|
||||
- Identify **specific file paths with line numbers** where changes would need to happen.
|
||||
- Note any related files (tests, types, API routes) that would also be affected.
|
||||
|
||||
## Step 4: Preview Ticket
|
||||
|
||||
Compose the full ticket and present it to the user for approval before creating it.
|
||||
|
||||
### Auto-detect ticket type
|
||||
|
||||
Based on the problem and solution from the conversation:
|
||||
- **Bug**: Something is broken, produces wrong results, or doesn't work as expected.
|
||||
- **Feature**: Something entirely new that doesn't exist yet.
|
||||
- **Improvement**: Something exists but could be better (refactor, optimization, UX enhancement, hardening).
|
||||
|
||||
### Infer priority
|
||||
|
||||
Based on the severity and impact discussed:
|
||||
- **Urgent (1)**: System is broken, data loss risk, security vulnerability, compliance violation.
|
||||
- **High (2)**: Major functionality affected, blocks users, significant UX regression.
|
||||
- **Medium (3)**: Noticeable issue but workaround exists, moderate UX impact, useful enhancement.
|
||||
- **Low (4)**: Minor polish, nice-to-have, cosmetic, non-blocking improvement.
|
||||
|
||||
### Ticket format
|
||||
|
||||
Present the preview in this format:
|
||||
|
||||
```
|
||||
## Ticket Preview
|
||||
|
||||
**Title**: [{Area}] {Short actionable title}
|
||||
**Type**: Bug / Feature / Improvement
|
||||
**Priority**: Urgent / High / Medium / Low
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
{What's wrong or what's missing — current state. Be specific with concrete examples.}
|
||||
|
||||
## Solution
|
||||
{What should be built or fixed — desired state. Be specific about the expected behavior.}
|
||||
|
||||
## Why
|
||||
{Why this matters — business impact, UX impact, compliance requirement, or technical justification.}
|
||||
|
||||
## Where
|
||||
{Affected files and components with paths and line numbers.}
|
||||
|
||||
**Files:**
|
||||
- `path/to/file.ts:L42` — {what changes here}
|
||||
- `path/to/other.ts:L15` — {what changes here}
|
||||
|
||||
**Related files:**
|
||||
- `path/to/test.ts` — tests to update
|
||||
- `types/index.ts` — types to add/modify
|
||||
|
||||
{Any additional implementation guidance.}
|
||||
|
||||
---
|
||||
*Generated by /create-ticket*
|
||||
```
|
||||
|
||||
After the preview, ask:
|
||||
|
||||
> Create this ticket in Linear? (yes / no / edit)
|
||||
|
||||
- **yes**: Proceed to Step 5.
|
||||
- **no**: Cancel — do not create the ticket.
|
||||
- **edit**: Ask what they want to change, update the preview, and ask again.
|
||||
|
||||
## Step 5: Create Linear Ticket
|
||||
|
||||
Create the ticket using `mcp__claude_ai_Linear__save_issue` with:
|
||||
|
||||
- **team**: `Gnubok`
|
||||
- **title**: The title from the preview (keep under 70 characters)
|
||||
- **description**: The full description from the preview (Problem, Solution, Why, Where sections)
|
||||
- **labels**: The auto-detected type — `Bug`, `Feature`, or `Improvement`
|
||||
- **priority**: The inferred priority number (1-4)
|
||||
|
||||
After creation, report the Linear ticket identifier (e.g., `GNO-123`) so the user can reference it.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Be specific and actionable. Vague tickets waste everyone's time.
|
||||
- Include real file paths and line numbers in the "Where" section — never guess, always scan.
|
||||
- Keep the title short and prefixed with the area in brackets.
|
||||
- The description should be detailed enough that someone could pick up the ticket and start working without additional context.
|
||||
- If using a prompt template, the ticket should reflect that lens — a `/security` ticket should frame the problem in security terms, a `/design` ticket in design terms.
|
||||
- Do not create the ticket without explicit user approval.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Bookkeeping Prompt
|
||||
|
||||
## Perspective
|
||||
|
||||
You are scanning for Swedish accounting compliance issues, bookkeeping logic gaps, and financial data handling problems. gnubok implements double-entry bookkeeping compliant with Bokforingslagen (BFL) and BFN standards. Focus on correctness of journal entries, VAT handling, account mappings, and legal guardrails.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Journal Entry Integrity
|
||||
- [ ] All journal entries route through `createJournalEntry()` in `lib/bookkeeping/engine.ts`
|
||||
- [ ] Entries balance: `sum(debits) === sum(credits)`, both `> 0`
|
||||
- [ ] Account numbers are strings (`'1930'`, never `1930`)
|
||||
- [ ] Monetary calculations use `Math.round(x * 100) / 100` (never `toFixed()`)
|
||||
- [ ] Voucher numbers assigned via DB RPC (never set manually)
|
||||
- [ ] Posted entries are never edited (storno pattern for corrections)
|
||||
- [ ] `reverseEntry()` used for cancellation, `correctEntry()` for corrections
|
||||
|
||||
### VAT Handling
|
||||
- [ ] Correct VAT treatment applied per transaction type (`standard_25`, `reduced_12`, `reduced_6`, `reverse_charge`, `export`, `exempt`)
|
||||
- [ ] Mixed-rate invoices handled via `generatePerRateLines()` in `invoice-entries.ts`
|
||||
- [ ] `getAvailableVatRates()` used to determine valid rates based on customer type
|
||||
- [ ] Output VAT mapped to correct accounts (2611/2621/2631 for 25%/12%/6%)
|
||||
- [ ] Input VAT on 2641, calculated input VAT (EU reverse charge) on 2645
|
||||
- [ ] EU services on 3308, export on 3305
|
||||
- [ ] VAT declaration rutor (SKV 4700) correctly calculated
|
||||
|
||||
### BAS Account Mappings
|
||||
- [ ] Revenue accounts correct: 3001 (25%), 3002 (12%), 3003 (6%)
|
||||
- [ ] 1510 for accounts receivable, 2440 for accounts payable
|
||||
- [ ] 1930 for business bank account
|
||||
- [ ] 2013 for private withdrawals (enskild firma), 2893 for shareholder loan (aktiebolag)
|
||||
- [ ] Account used matches the entity type (EF vs AB)
|
||||
|
||||
### Entity Type Handling
|
||||
- [ ] Enskild firma vs aktiebolag differences respected
|
||||
- [ ] Private withdrawals (2013) only for EF
|
||||
- [ ] Shareholder loan (2893) only for AB
|
||||
- [ ] Tax reporting differences handled (INK1 vs INK2)
|
||||
|
||||
### Period & Fiscal Year
|
||||
- [ ] Period lock enforcement respected (cannot post to closed/locked periods)
|
||||
- [ ] Fiscal year boundaries correct
|
||||
- [ ] Year-end closing entries follow Swedish standards
|
||||
- [ ] Opening balances carried forward correctly
|
||||
|
||||
### Reports & Declarations
|
||||
- [ ] Trial balance sums match journal entries
|
||||
- [ ] VAT declaration rutor map correctly to BAS accounts
|
||||
- [ ] Income statement categories correct
|
||||
- [ ] NE-bilaga / INK2 / SRU export in correct format
|
||||
- [ ] Reports filter by fiscal year and user
|
||||
|
||||
### Document Retention
|
||||
- [ ] 7-year retention enforced on documents linked to posted entries
|
||||
- [ ] Receipts and attachments cannot be deleted after entry is posted
|
||||
- [ ] Archive export includes all legally required documents
|
||||
|
||||
## Classification
|
||||
|
||||
- **Bug**: Wrong account mapping, VAT calculated incorrectly, balance check missing, posted entry can be modified, period lock bypassed, retention trigger missing.
|
||||
- **Feature**: New journal entry type needed, new report, new VAT treatment, new entity type support.
|
||||
- **Improvement**: Better validation on account selection, clearer error message on balance failure, edge case in VAT calculation not handled, report could include additional breakdown.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Database Prompt
|
||||
|
||||
## Perspective
|
||||
|
||||
You are scanning for database schema, migration, and query issues in a Supabase PostgreSQL application with RLS. Focus on data integrity, performance, security policies, and compliance with Swedish accounting law (BFL 7-year retention, period locks, immutable posted entries).
|
||||
|
||||
## Checklist
|
||||
|
||||
### Schema Design
|
||||
- [ ] Tables have UUID primary keys (`DEFAULT uuid_generate_v4()`)
|
||||
- [ ] `user_id UUID REFERENCES auth.users ON DELETE CASCADE NOT NULL` on all user-owned tables
|
||||
- [ ] `updated_at` trigger using `update_updated_at_column()` on all tables
|
||||
- [ ] Appropriate NOT NULL constraints on required fields
|
||||
- [ ] CHECK constraints for enum-like fields (status, type columns)
|
||||
- [ ] Foreign keys with appropriate ON DELETE behavior (CASCADE vs RESTRICT)
|
||||
|
||||
### Row Level Security
|
||||
- [ ] RLS enabled on every table
|
||||
- [ ] SELECT policy: `auth.uid() = user_id`
|
||||
- [ ] INSERT policy: `auth.uid() = user_id`
|
||||
- [ ] UPDATE policy: `auth.uid() = user_id`
|
||||
- [ ] No overly permissive policies (e.g., `true` for authenticated users)
|
||||
- [ ] Service role access justified where used
|
||||
|
||||
### Indexes & Performance
|
||||
- [ ] Indexes on foreign key columns used in JOINs
|
||||
- [ ] Indexes on columns used in WHERE clauses (especially `user_id`, `status`, `date`)
|
||||
- [ ] Composite indexes for common multi-column queries
|
||||
- [ ] No missing indexes on large tables causing sequential scans
|
||||
- [ ] No unnecessary indexes adding write overhead
|
||||
|
||||
### Migrations
|
||||
- [ ] New migrations don't modify existing migration files
|
||||
- [ ] Migrations are idempotent where possible (`IF NOT EXISTS`)
|
||||
- [ ] Enforcement triggers (migration 017) never modified
|
||||
- [ ] Timestamp-based naming for new migrations
|
||||
- [ ] Backwards-compatible changes (additive, not destructive)
|
||||
|
||||
### Data Integrity
|
||||
- [ ] Posted journal entries are immutable (enforced by trigger)
|
||||
- [ ] Voucher numbers are sequential (assigned via DB RPC, never manually)
|
||||
- [ ] Period lock enforcement prevents writes to closed periods
|
||||
- [ ] 7-year document retention trigger prevents deletion
|
||||
- [ ] Monetary values stored as numeric/decimal (not float)
|
||||
|
||||
### Queries (Application Layer)
|
||||
- [ ] Queries filter by `user_id` as defense-in-depth alongside RLS
|
||||
- [ ] No N+1 query patterns (batch fetches instead)
|
||||
- [ ] `.select()` specifies columns (not `select('*')` on wide tables)
|
||||
- [ ] Supabase `.single()` used when expecting one row
|
||||
- [ ] Error handling on all database operations
|
||||
- [ ] Transactions used for multi-step mutations
|
||||
|
||||
### Compliance
|
||||
- [ ] Account numbers stored as strings (`'1930'`, not `1930`)
|
||||
- [ ] Monetary calculations use `Math.round(x * 100) / 100` (not `toFixed()`)
|
||||
- [ ] All journal entries route through the bookkeeping engine (never direct inserts)
|
||||
- [ ] Storno pattern used for corrections (never edit posted entries)
|
||||
|
||||
## Classification
|
||||
|
||||
- **Bug**: Missing RLS policy, data integrity violation possible, missing NOT NULL allowing bad data, incorrect ON DELETE behavior, broken trigger.
|
||||
- **Feature**: New table needs migration, new index for a new query pattern, new RPC function needed.
|
||||
- **Improvement**: Missing index on existing table, overly broad SELECT, N+1 query could be batched, schema could use a CHECK constraint.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Design Prompt
|
||||
|
||||
## Perspective
|
||||
|
||||
You are scanning for design and UX issues in the gnubok interface. The app follows a minimal, sharp, efficient aesthetic inspired by Mercury (banking). Evaluate against the gnubok design system: grayscale palette with sage green/terracotta/ochre accents, Fraunces headings, Geist body, generous whitespace, subtle motion.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Consistency & Design System
|
||||
- [ ] Spacing values use Tailwind scale (not arbitrary values)
|
||||
- [ ] Colors are from the design palette (grayscale, sage green, terracotta, ochre)
|
||||
- [ ] Fraunces used for display headings, Geist for body text
|
||||
- [ ] `tabular-nums` applied to all financial/numeric data
|
||||
- [ ] shadcn/ui components used where appropriate
|
||||
- [ ] Icon sizes consistent (15px nav, larger for empty states)
|
||||
- [ ] Border styles consistent (subtle, 60% opacity)
|
||||
|
||||
### Loading & Empty States
|
||||
- [ ] Pages have loading states (skeletons preferred over spinners)
|
||||
- [ ] Empty states exist with message and CTA when no data
|
||||
- [ ] Loading states match the layout of the loaded content
|
||||
|
||||
### Error Handling UI
|
||||
- [ ] Error boundaries or error states shown to the user
|
||||
- [ ] Forms show inline validation errors
|
||||
- [ ] Error messages are helpful and in Swedish
|
||||
|
||||
### Animation & Motion
|
||||
- [ ] List items stagger-animate on entry
|
||||
- [ ] Interactive elements have hover/active transitions
|
||||
- [ ] Transitions use appropriate easing (spring for feedback, ease for reveals)
|
||||
- [ ] `prefers-reduced-motion` respected
|
||||
- [ ] No abrupt state changes that need transitions
|
||||
|
||||
### Accessibility
|
||||
- [ ] Visible focus rings on interactive elements
|
||||
- [ ] WCAG AA contrast (4.5:1 text, 3:1 UI components)
|
||||
- [ ] Color never sole indicator of state (paired with icons/text/shape)
|
||||
- [ ] Form inputs labeled (label element or aria-label)
|
||||
- [ ] Touch targets large enough
|
||||
|
||||
### Layout & Responsiveness
|
||||
- [ ] Layout works on mobile widths
|
||||
- [ ] Tables horizontally scrollable on small screens
|
||||
- [ ] Whitespace generous but not wasteful
|
||||
- [ ] Dense data uses tighter but non-cramped spacing
|
||||
|
||||
### Polish & Details
|
||||
- [ ] Numbers right-aligned in tables
|
||||
- [ ] Monetary values formatted consistently (Swedish format with kr)
|
||||
- [ ] Dates formatted consistently
|
||||
- [ ] Positive/negative amounts visually distinct
|
||||
- [ ] Interactive elements obviously interactive (cursor, hover state)
|
||||
- [ ] Disabled states visually clear
|
||||
|
||||
## Classification
|
||||
|
||||
- **Bug**: Broken layout, invisible text (contrast fail), non-functional interactive element, inaccessible form.
|
||||
- **Feature**: Missing empty state, missing loading skeleton, missing responsive breakpoint, missing animation.
|
||||
- **Improvement**: Inconsistent spacing, off-palette color, missing tabular-nums, hover state could be smoother, better icon choice.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Performance Prompt
|
||||
|
||||
## Perspective
|
||||
|
||||
You are scanning for performance issues in a Next.js 16 (App Router) + React 19 + Supabase application. Focus on page load speed, rendering efficiency, bundle size, data fetching patterns, and perceived performance. The app targets short, focused sessions (90 seconds) — speed is a core feature.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Rendering & React
|
||||
- [ ] Components that don't need interactivity are Server Components (no unnecessary `'use client'`)
|
||||
- [ ] Large lists use virtualization or pagination (not rendering 1000+ items)
|
||||
- [ ] Expensive computations wrapped in `useMemo` where re-renders are frequent
|
||||
- [ ] Callback functions stable with `useCallback` when passed as props to memoized children
|
||||
- [ ] No unnecessary re-renders from unstable object/array references in props or context
|
||||
- [ ] Suspense boundaries with appropriate fallbacks for async components
|
||||
|
||||
### Data Fetching
|
||||
- [ ] Server Components fetch data on the server (not client-side `useEffect` for initial data)
|
||||
- [ ] No waterfall fetches (parallel when independent)
|
||||
- [ ] Pagination or cursor-based loading for large datasets
|
||||
- [ ] No fetching data that's already available from a parent component
|
||||
- [ ] API routes return only needed fields (not entire rows with unused columns)
|
||||
- [ ] Appropriate use of `revalidatePath` / `revalidateTag` for cache invalidation
|
||||
|
||||
### Bundle Size
|
||||
- [ ] Heavy libraries imported dynamically (`next/dynamic`) where not needed on initial load
|
||||
- [ ] No duplicate dependencies (same library imported from different paths)
|
||||
- [ ] Tree-shaking friendly imports (`import { X } from 'lib'` not `import * as lib`)
|
||||
- [ ] Images optimized with `next/image` (not raw `<img>` tags)
|
||||
- [ ] SVG icons from Lucide imported individually (not the entire icon set)
|
||||
|
||||
### Database & API
|
||||
- [ ] Queries have appropriate indexes (see database prompt for details)
|
||||
- [ ] No N+1 patterns (fetching related data in loops)
|
||||
- [ ] Batch operations used where possible (bulk insert/update)
|
||||
- [ ] API responses are reasonably sized (not returning megabytes of data)
|
||||
- [ ] Rate limiting in place for expensive operations
|
||||
|
||||
### Perceived Performance
|
||||
- [ ] Loading skeletons match content layout (not generic spinners)
|
||||
- [ ] Optimistic UI updates for user actions (don't wait for server response to show feedback)
|
||||
- [ ] Transitions between states are smooth (no jarring layout shifts)
|
||||
- [ ] Critical content above the fold loads first
|
||||
- [ ] Non-critical content lazy-loaded or deferred
|
||||
|
||||
### Caching
|
||||
- [ ] Static pages/routes use appropriate caching headers
|
||||
- [ ] API responses include cache headers where data doesn't change frequently
|
||||
- [ ] Client-side state management avoids redundant fetches
|
||||
- [ ] Supabase real-time subscriptions used only where needed (not as a polling replacement)
|
||||
|
||||
## Classification
|
||||
|
||||
- **Bug**: Memory leak, infinite re-render loop, blocking the main thread, N+1 causing timeouts.
|
||||
- **Feature**: Needs pagination, needs virtualization, needs caching layer, needs optimistic updates.
|
||||
- **Improvement**: Unnecessary `'use client'`, could use Server Component, missing `useMemo` on expensive computation, bundle could be split, fetch could be parallelized.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Security Prompt
|
||||
|
||||
## Perspective
|
||||
|
||||
You are scanning for security vulnerabilities and hardening opportunities in a Next.js 16 + Supabase application. Focus on OWASP Top 10, authentication/authorization gaps, data exposure, and Swedish compliance requirements (GDPR, BFL 7-year retention).
|
||||
|
||||
## Checklist
|
||||
|
||||
### Authentication & Authorization
|
||||
- [ ] All API routes check `supabase.auth.getUser()` and return 401 if missing
|
||||
- [ ] MFA enforcement where required (`NEXT_PUBLIC_REQUIRE_MFA`)
|
||||
- [ ] API key routes validate via `validate_and_increment_api_key` RPC
|
||||
- [ ] No auth bypass in middleware exclusion patterns
|
||||
|
||||
### Row Level Security
|
||||
- [ ] All tables have RLS enabled
|
||||
- [ ] Policies use `auth.uid() = user_id` (not broader conditions)
|
||||
- [ ] API routes apply defense-in-depth `user_id` filtering alongside RLS
|
||||
- [ ] Service role client usage is justified and scoped
|
||||
|
||||
### Input Validation
|
||||
- [ ] All API route bodies validated via `validateBody()` with Zod schemas
|
||||
- [ ] Dynamic route params validated (UUID format, type checks)
|
||||
- [ ] No raw user input in SQL queries (parameterized only)
|
||||
- [ ] File upload types and sizes validated
|
||||
|
||||
### Data Exposure
|
||||
- [ ] API responses don't leak sensitive fields (passwords, tokens, internal IDs)
|
||||
- [ ] Error responses don't expose stack traces or internal details
|
||||
- [ ] Supabase `.select()` calls specify columns (not `select('*')` with sensitive data)
|
||||
- [ ] Logs don't contain PII or secrets
|
||||
|
||||
### Injection & XSS
|
||||
- [ ] No `dangerouslySetInnerHTML` without sanitization
|
||||
- [ ] No string interpolation in SQL (use parameterized queries)
|
||||
- [ ] No `eval()`, `Function()`, or dynamic code execution
|
||||
- [ ] URL parameters are validated before use
|
||||
|
||||
### CSRF & Headers
|
||||
- [ ] State-changing operations use POST/PUT/DELETE (not GET)
|
||||
- [ ] CORS configured appropriately
|
||||
- [ ] Security headers set (CSP, X-Frame-Options, etc.)
|
||||
|
||||
### Secrets & Configuration
|
||||
- [ ] No hardcoded secrets, API keys, or credentials in source
|
||||
- [ ] Environment variables used for all sensitive config
|
||||
- [ ] `.env` files in `.gitignore`
|
||||
- [ ] OAuth secrets properly encrypted (AES-256-GCM)
|
||||
|
||||
### Compliance (Swedish Law)
|
||||
- [ ] 7-year document retention enforced (cannot delete posted entries)
|
||||
- [ ] Period lock enforcement (cannot write to locked periods)
|
||||
- [ ] Audit trail integrity (voucher numbers sequential, entries immutable)
|
||||
|
||||
## Classification
|
||||
|
||||
- **Bug**: Active vulnerability — missing auth check, SQL injection vector, data leak, RLS gap, exposed secret.
|
||||
- **Feature**: New security capability needed — audit logging, rate limiting on a new endpoint, CSP policy addition.
|
||||
- **Improvement**: Hardening — adding input validation to an endpoint that works but accepts too broadly, tightening a SELECT to specific columns, adding rate limiting.
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -26,6 +26,7 @@ export default function BankingSettingsPanel() {
|
||||
const [syncingConnectionId, setSyncingConnectionId] = useState<string | null>(null)
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const [connectingBankName, setConnectingBankName] = useState<string | null>(null)
|
||||
const connectingRef = useRef(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [showCsvFallback, setShowCsvFallback] = useState(false)
|
||||
|
||||
@@ -49,6 +50,8 @@ export default function BankingSettingsPanel() {
|
||||
}
|
||||
|
||||
async function handleConnectBank(bank: Bank) {
|
||||
if (connectingRef.current) return
|
||||
connectingRef.current = true
|
||||
setIsConnecting(true)
|
||||
setConnectingBankName(bank.name)
|
||||
|
||||
@@ -92,6 +95,7 @@ export default function BankingSettingsPanel() {
|
||||
description: error instanceof Error ? error.message : 'Kunde inte ansluta bank',
|
||||
variant: 'destructive',
|
||||
})
|
||||
connectingRef.current = false
|
||||
setIsConnecting(false)
|
||||
setConnectingBankName(null)
|
||||
setShowCsvFallback(true)
|
||||
|
||||
@@ -101,18 +101,37 @@ export const enableBankingExtension: Extension = {
|
||||
psu_type: psuType,
|
||||
})
|
||||
|
||||
// Clean up any stale pending connections for this user+bank
|
||||
// to avoid conflicts with the new authorization
|
||||
const { data: staleConnections } = await supabase
|
||||
// Reject if there's already a recent pending connection for this user+bank
|
||||
// to prevent double-click race conditions that confuse the bank's consent flow
|
||||
const { data: recentPending } = await supabase
|
||||
.from('bank_connections')
|
||||
.select('id')
|
||||
.select('id, created_at')
|
||||
.eq('user_id', user.id)
|
||||
.eq('bank_name', aspsp_name)
|
||||
.eq('status', 'pending')
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle()
|
||||
|
||||
if (staleConnections && staleConnections.length > 0) {
|
||||
if (recentPending) {
|
||||
const pendingAge = Date.now() - new Date(recentPending.created_at).getTime()
|
||||
const STALE_THRESHOLD_MS = 5 * 60 * 1000 // 5 minutes
|
||||
|
||||
if (pendingAge < STALE_THRESHOLD_MS) {
|
||||
log.info('[enable-banking] Rejecting duplicate connect — recent pending exists', {
|
||||
existing_id: recentPending.id,
|
||||
age_ms: pendingAge,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'En anslutning pågår redan. Vänta och försök igen.' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
// Clean up stale pending connections (older than threshold)
|
||||
log.info('[enable-banking] Cleaning up stale pending connections', {
|
||||
count: staleConnections.length,
|
||||
stale_id: recentPending.id,
|
||||
age_ms: pendingAge,
|
||||
})
|
||||
await supabase
|
||||
.from('bank_connections')
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Add last_integrity_check_at column for document integrity verification cron
|
||||
-- Tracks when each document was last verified, allowing the cron to prioritize
|
||||
-- unchecked or least-recently-checked documents.
|
||||
|
||||
ALTER TABLE public.document_attachments
|
||||
ADD COLUMN last_integrity_check_at timestamptz;
|
||||
|
||||
-- Index for efficient ordering in the verification cron (nulls first = unchecked prioritized)
|
||||
CREATE INDEX idx_document_attachments_integrity_check
|
||||
ON public.document_attachments (last_integrity_check_at ASC NULLS FIRST)
|
||||
WHERE is_current_version = true;
|
||||
Reference in New Issue
Block a user