summaryrefslogtreecommitdiff
path: root/apps/web/app/api/v1/keys/route.ts
blob: 7ac71443e41354e8ee55850c3fb8e8c4025eff50 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { NextResponse } from "next/server"
import { createSupabaseServerClient } from "@/lib/supabase/server"
import { createSupabaseAdminClient } from "@/lib/supabase/admin"
import { generateApiKey } from "@/lib/api-key"
import { TIER_LIMITS, type SubscriptionTier } from "@asa-news/shared"
import { rateLimit } from "@/lib/rate-limit"

const MAXIMUM_ACTIVE_KEYS = 5

export async function GET() {
  const supabaseClient = await createSupabaseServerClient()
  const {
    data: { user },
  } = await supabaseClient.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 })
  }

  const adminClient = createSupabaseAdminClient()
  const { data: keys, error } = await adminClient
    .from("api_keys")
    .select("id, key_prefix, label, created_at, last_used_at, revoked_at")
    .eq("user_id", user.id)
    .order("created_at", { ascending: false })

  if (error) {
    return NextResponse.json(
      { error: "Failed to load API keys" },
      { status: 500 }
    )
  }

  return NextResponse.json({
    keys: keys.map((key) => ({
      identifier: key.id,
      keyPrefix: key.key_prefix,
      label: key.label,
      createdAt: key.created_at,
      lastUsedAt: key.last_used_at,
      isRevoked: key.revoked_at !== null,
    })),
  })
}

export async function POST(request: Request) {
  const supabaseClient = await createSupabaseServerClient()
  const {
    data: { user },
  } = await supabaseClient.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 })
  }

  const rateLimitResult = rateLimit(`api-keys:${user.id}`, 10, 60_000)
  if (!rateLimitResult.success) {
    return NextResponse.json({ error: "Too many requests" }, { status: 429 })
  }

  const adminClient = createSupabaseAdminClient()

  const { data: userProfile } = await adminClient
    .from("user_profiles")
    .select("tier")
    .eq("id", user.id)
    .single()

  if (
    !userProfile ||
    !TIER_LIMITS[userProfile.tier as SubscriptionTier]?.allowsApiAccess
  ) {
    return NextResponse.json(
      { error: "API access requires the developer plan" },
      { status: 403 }
    )
  }

  const { count: activeKeyCount } = await adminClient
    .from("api_keys")
    .select("id", { count: "exact", head: true })
    .eq("user_id", user.id)
    .is("revoked_at", null)

  if ((activeKeyCount ?? 0) >= MAXIMUM_ACTIVE_KEYS) {
    return NextResponse.json(
      { error: `Maximum of ${MAXIMUM_ACTIVE_KEYS} active keys allowed` },
      { status: 400 }
    )
  }

  const body = await request.json().catch(() => ({}))
  const label = typeof body.label === "string" ? body.label.trim() || null : null

  const { fullKey, keyHash, keyPrefix } = generateApiKey()

  const { error: insertError } = await adminClient.from("api_keys").insert({
    user_id: user.id,
    key_hash: keyHash,
    key_prefix: keyPrefix,
    label,
  })

  if (insertError) {
    return NextResponse.json(
      { error: "Failed to create API key" },
      { status: 500 }
    )
  }

  return NextResponse.json({
    key: fullKey,
    keyPrefix,
    label,
  })
}