Skip to content

Releases: hypercerts-org/certified-group-service

v0.5.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 27 Jun 17:56
d2b13e9

Who should read this release

Minor Changes

  • #61 4a73f38 Thanks @aspiers! - Operators can reassign a group's owner through a new admin API.

    Affects: Operators

    This adds an operator-only admin endpoint. It is deliberately not part of the member-facing API — it is authenticated with the service admin password, not a member's JWT or API key — so client app developers building on the group service have nothing to call or adapt; only operators running an instance are affected.

    Operators: a new optional env var CGS_ADMIN_PASSWORD enables operator-only admin endpoints (app.certified.group.admin.*), authenticated with HTTP Basic auth rather than group membership — the same mechanism the upstream AT Protocol reference PDS uses for com.atproto.admin.*. When unset, all admin endpoints are disabled. The trust model is the one a PDS operator already operates under, applied to the groups CGS hosts; admin actions are audit-logged. The first such endpoint, app.certified.group.admin.setOwner, reassigns a group's owner (including installing an owner who is not yet a member, for operator recovery when the incumbent is unavailable). See docs/deployment.md#admin-endpoints and the API reference for configuration and the endpoint contract.

v0.4.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 24 Jun 14:24
42a192c

Who should read this release

Minor Changes

  • #58 fb0e253 Thanks @aspiers! - Allow all group members to create, list, and revoke their own API keys. Owners
    retain group-wide visibility and revocation, while API-key auth itself still
    cannot manage keys.

    Affects: Client app developers

v0.3.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 23 Jun 15:27
29f4b48

Who should read this release

Minor Changes

  • #34 e6c8f87 Thanks @aspiers! - Backend services can now use revocable API keys for group-owned records.

    Affects: Client app developers, Operators

    Client app developers: group owners can create, list, and revoke keys with app.certified.group.keys.create, app.certified.group.keys.list, and app.certified.group.keys.delete. Key auth uses X-API-Key and supports explicit scopes plus reusable permission-set scopes such as include:org.hypercerts.authWrite, so apps no longer need to list every record permission by hand. See docs/design/api-keys.md, docs/design/api-key-permission-sets.md, and docs/integration-guide.md for request shapes, supported permissions, and examples.

    Operators: no new environment variables or manual migration are required. Leaked keys are recognizable by the cgsk_ prefix and can be revoked by the group owner.

Patch Changes

  • #43 a059e0f Thanks @aspiers! - Authentication failures are now logged server-side, so an operator can diagnose a rejected request from the logs instead of having to reproduce it.

    Affects: Operators

    Operators: every auth rejection in the verifier now emits a warn-level log line "Auth verification failed" before the request is refused. Previously the fallback error handler returned the error to the client but logged nothing, so a production 401 (e.g. Invalid audience) left no server-side trace of who called or which group they targeted.

    • JWT (service-auth) failures mostly log { reason, nsid, jwt: { header, payload } }. The JWT is decoded for logging without verifying its signature, and the signature segment is dropped — it is a bearer credential and is never written to the logs (jwt is null for a token that is not a well-formed three-part base64url JWT). The exception is Missing auth token, which fires before any token exists and logs only { reason, path }. reason is one of: Missing auth token, verifyJwt threw, Token lifetime check failed, jwt audience does not match service did, repo did not resolve to a known group, Invalid audience, Missing jti, Replayed token.
    • API-key failures log { reason, authKind: 'apiKey', keyRef, groupDid } — only the non-secret key reference, never the raw X-API-Key value. reason is one of: Malformed API key, Missing repo for API-key request, repo did not resolve to a known group, Invalid API key, Corrupt API-key scopes.
    • No request that previously succeeded is affected, and the HTTP responses returned to clients are unchanged — this is purely additional logging at the existing logLevel.

v0.2.1

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 08 Jun 18:13
9fe9609

Patch Changes

  • Republish the container image. The v0.2.0 image build failed (its version-stamping step had no .cgs-version to read), so no ghcr.io/hypercerts-org/group-service tags were published for v0.2.0. The publish workflow now stamps the version before building; v0.2.1 is the first release to produce a working image. No functional change to the service versus what v0.2.0 would have shipped.

    Affects: Operators

v0.2.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 08 Jun 17:12
5c880a4

Who should read this release

Minor Changes

  • #33 677298e Thanks @aspiers! - Apps now name the target group with an explicit repo field instead of overloading the service-auth token's audience.

    Affects: Client app developers, Operators

    Client app developers: group-scoped methods take a repo field (a handle or DID) naming the target group, with the JWT aud set to the service DID — the shape a stock @atproto/api client already emits. The old form (group in aud, no repo) still works but is deprecated.

    Legacy (deprecated) New (supported)
    Group named by JWT aud explicit repo
    JWT aud the group DID the service DID
    repo field absent present
    Deprecation header Deprecation: true + Link none
    • Behaviour change to adapt to now: repo is the group selector, not a cross-check — the old 403 "repo field must match the group DID" is gone; a repo naming no registered group returns 401 Unknown group. RBAC is unchanged: you can only target groups you have a role in.
    • How to migrate (per-method repo placement, the coupled repo+aud switch, non-proxied vs proxied calls, detecting un-migrated calls): see docs/aud-migration.md. Design rationale and security analysis: docs/design/aud-deprecation.md.

    Operators: no new environment variables and no migration; SERVICE_DID is unchanged. The service now serves its did:web document at GET /.well-known/did.json (a public, unauthenticated route, sibling to /health); it must be publicly reachable, since that is how the service DID resolves and how service proxying targets the service. A rate-limited warn log flags any client still on the legacy form — one line per caller per 15 minutes.

  • #30 e4a0aac Thanks @aspiers! - A group's owner can now remove the group from the service.

    Affects: End users, Client app developers, Operators

    End users: if you are a group's owner, you can now delete the group from the service. This removes the group and its membership from the service only — the underlying account and its data are left untouched, so the account continues to exist and could be added back later. Only the owner can do this; admins and members cannot.

    Client app developers: a new procedure app.certified.group.destroy removes a group from the service, gated on the owner role (new RBAC operation group.destroy). It removes only the service's record of the group; the underlying PDS account is left intact, so the account can be re-imported afterwards via app.certified.group.import. Call it group-scoped, the same auth style as the other per-group methods. Full request/response and errors are in docs/api-reference.md and docs/integration-guide.md.

    Heads-up: like the other per-group methods, destroy currently names the target group via the JWT aud. That overload is being deprecated (issue #27) in favour of an explicit request-level group field, with aud reverting to the group service's own DID. The aud = group DID form will be supported during a transition window and then removed — build against the explicit-group form once it ships.

    Operators: destroy is served on /xrpc/app.certified.group.destroy; no new environment variables. It deletes the group's groups row and member_index entries (in one transaction) and then the per-group SQLite file under data/groups/. The destroy is recorded in the service log, not in the (deleted) per-group audit log.

  • #30 f265075 Thanks @aspiers! - You can now turn an existing account into a group, instead of always creating a brand-new one.

    Affects: Client app developers, Operators

    Client app developers: a new procedure app.certified.group.import is the sibling of app.certified.group.register — it reuses an existing account instead of creating one. You supply the account's app password so the service can act on its behalf, and the JWT must be signed by the account being imported (iss = the account's DID), not by the prospective owner. Two consequences worth knowing: the service holds no recovery key for an imported account (the owner's own credentials are their credible exit), and import does not modify the account's DID document. Full request/response, auth model, and errors are in docs/api-reference.md (Group lifecycle), docs/integration-guide.md (Step 1b), and docs/design/group-import.md.

    Operators: import is served on /xrpc/app.certified.group.import (service-auth, like register); no new environment variables. Imported groups are stored in the groups table with encrypted_recovery_key left NULL, distinguishing them from registered groups, and are driven via the per-group pds_url resolved at import time — which may differ from GROUP_PDS_URL.

  • #35 a906bd8 Thanks @aspiers! - The health check now reports the running service version, and the same check is also reachable at /xrpc/_health.

    Affects: Client app developers, Operators

    Client app developers:

    • GET /health response gains two fields: service (always "group-service") and version (e.g. "0.1.0+90d10b96"). The existing status: "ok" / 503 { status: "error", message: "database unreachable" } behaviour is unchanged, so existing health checks keep working.
    • A new GET /xrpc/_health route returns the identical body to /health (including the same 503-on-DB-failure semantics). This mirrors the upstream PDS convention of exposing /xrpc/_health; the group service has no upstream PDS, so it serves the route itself. Note it returns the full { status, service, version } object, not the bare { version } some atproto services return.

    Operators:

    • The reported version resolves in this order: the CGS_VERSION env var, then a .cgs-version file written at image-build time, then the version field in package.json. Set CGS_VERSION to override the stamp (e.g. CGS_VERSION=0.1.0+abcdef01).
    • On Railway, the Docker build stamps <package.json version>+<short commit sha> automatically from RAILWAY_GIT_COMMIT_SHA — no action needed.
    • For local docker build, run ./scripts/stamp-version.sh first to write .cgs-version; the build fails with ERROR: .cgs-version not found otherwise. .cgs-version is gitignored.