• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hypercerts-org / certified-group-service / 27154941116

08 Jun 2026 05:23PM UTC coverage: 92.919% (+0.7%) from 92.257%
27154941116

Pull #34

github

web-flow
Merge 2d82ec2de into 5c880a488
Pull Request #34: feat(auth): generalised API-key framework for CGS XRPCs (#26); serve did:web document (#29)

538 of 589 branches covered (91.34%)

Branch coverage included in aggregate %.

483 of 502 new or added lines in 17 files covered. (96.22%)

1 existing line in 1 file now uncovered.

2047 of 2193 relevant lines covered (93.34%)

50.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.71
/src/api/keys/create.ts
1
import type { Server } from '@atproto/xrpc-server'
1✔
2
import { XRPCError } from '@atproto/xrpc-server'
1✔
3
import type { AppContext } from '../../context.js'
4
import {
5
  registerAuthedMethod,
6
  jsonResponse,
7
  resolveGroupDid,
8
  assertCanWithAudit,
9
  sqliteToIso,
10
} from '../util.js'
1✔
11
import { generateApiKey } from '../../auth/api-key.js'
1✔
12
import { canonicalizeScopes } from '../../auth/scopes.js'
1✔
13

14
export default function (server: Server, ctx: AppContext) {
8✔
15
  registerAuthedMethod(server, 'app.certified.group.keys.create', ctx, {
8✔
16
    handler: async ({ auth, input }) => {
8✔
17
      const { callerDid, authKind, scopes: callerScopes, apiKeyRef } = auth.credentials
6✔
18
      // Default to {} so the destructure can't 500 on an absent body (the
19
      // framework already rejects missing required input with a 400).
20
      const { repo, name, scopes } = (input?.body ?? {}) as {
6!
21
        repo?: string
22
        name?: string
23
        scopes?: string[]
24
      }
25

26
      if (typeof name !== 'string' || name.length === 0) {
6!
NEW
27
        throw new XRPCError(400, 'name is required', 'InvalidRequest')
×
NEW
28
      }
×
29
      if (!Array.isArray(scopes) || scopes.length === 0) {
6!
NEW
30
        throw new XRPCError(400, 'At least one scope is required', 'InvalidRequest')
×
NEW
31
      }
×
32

33
      // Canonicalize to this service's stored form: a key only ever calls the
34
      // CGS it was minted on, so we append our own scope `aud`. A friendly
35
      // `rpc:<lxm>` is expanded; an already-canonical scope is accepted only if
36
      // its `aud` is ours — a foreign service DID or wrong service fragment is
37
      // rejected rather than stored as a dead grant.
38
      const canon = canonicalizeScopes(scopes, ctx.config.serviceDid)
6✔
39
      if (!canon.ok) {
6✔
40
        throw new XRPCError(400, `Invalid scope: ${canon.scope} (${canon.reason})`, 'InvalidScope')
2✔
41
      }
2✔
42
      const storedScopes = canon.scopes
4✔
43

44
      const groupDid = await resolveGroupDid(ctx, auth.credentials, repo)
4✔
45
      const groupDb = ctx.groupDbs.get(groupDid)
4✔
46

47
      // Owner-only. Passing the principal means an apiKey caller is rejected here
48
      // too: keys.create has no scope mapping, so the scope check denies it — a
49
      // key cannot mint keys in iteration 1.
50
      await assertCanWithAudit(ctx, groupDb, callerDid, 'keys.create', undefined, {
4✔
51
        authKind,
4✔
52
        scopes: callerScopes,
4✔
53
        apiKeyRef,
4✔
54
      })
4✔
55

56
      const key = generateApiKey()
2✔
57

58
      // Insert and read created_at back: datetime('now') is only resolved at
59
      // step time, so we cannot know it without selecting it.
60
      const inserted = await groupDb
2✔
61
        .insertInto('group_api_keys')
2✔
62
        .values({
2✔
63
          key_ref: key.keyRef,
2✔
64
          key_hash: key.hash,
2✔
65
          name,
2✔
66
          scopes: JSON.stringify(storedScopes),
2✔
67
          created_by: callerDid,
2✔
68
        })
2✔
69
        .returning('created_at')
2✔
70
        .executeTakeFirstOrThrow()
2✔
71

72
      // `createdKeyRef` (the key this action created), distinct from `apiKeyRef`
73
      // which attributes an action performed *by* a key (see assertCanWithAudit).
74
      await ctx.audit.log(groupDb, callerDid, 'keys.create', 'permitted', {
2✔
75
        createdKeyRef: key.keyRef,
2✔
76
        name,
2✔
77
        scopes: storedScopes,
2✔
78
      })
2✔
79

80
      return jsonResponse({
2✔
81
        keyRef: key.keyRef,
2✔
82
        key: key.plaintext, // returned exactly once
2✔
83
        scopes: storedScopes,
2✔
84
        createdAt: sqliteToIso(inserted.created_at),
2✔
85
      })
2✔
86
    },
6✔
87
  })
8✔
88
}
8✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc