ProductSettings

Settings

Workspace RBAC, session login, DSN keys, and webhook create/test curls for Epure self-host.

Workspace settings (you / team) and project settings (DSN, retention, webhooks). Dashboard APIs need a session; Postgres RLS scopes rows with app.current_org_id.

How do I sign in?

Email and password at /login, or Google when GOOGLE_CLIENT_ID is set. Dashboard /api/v1/* needs the resulting cookie.

Billing tab

/billing is a Cloud placeholder. OSS ops: ignore it. Cloud later: Pro $24/mo, Plus $79/mo.

How do I open a session?

Password login returns 204 and Set-Cookie (Epure.sid on local HTTP). DSN headers on these routes return 401.

Sign in with email

Seed user after ./scripts/seed-dev.sh. Register at /login in production.

command

curl -sS -c cookies.txt -D - -o /dev/null -X POST \
"http://localhost:8080/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"dev@epure.local","password":"devpassword"}'

Expected

HTTP/1.1 204 No Content
Set-Cookie: Epure.sid=...; HttpOnly; Path=/; SameSite=Lax
ResultWhat happenedWhat to do
204 + cookieSession establishedReuse -b cookies.txt on /api/v1/*
401Bad email or passwordRegister, or re-run seed-dev
429 plain text20 login/register req / IP / 60sWait
Later 401 on /api/v1Cookie rejected (Secure vs HTTP)Local HTTP: EPURE_SESSION_SECURE=0. Troubleshooting

Full auth table: Authentication.

What is on workspace settings?

RoutePurpose
/settingsProfile / general account
/settings/securityPassword and sessions
/teamMembers, invites, roles
/usageUsage overview
/billingCloud placeholder

Sign-in at /login: email + password (argon2id), or Google when GOOGLE_CLIENT_ID is set.

Session cookies: Epure.sid on HTTP (EPURE_SESSION_SECURE=0), __Host-epure.sid behind HTTPS (EPURE_SESSION_SECURE=1).

Roles (RBAC lite)

RoleCan
OwnerEverything Admin can + delete projects, full workspace control
AdminProjects, DSN keys, webhooks, invites, project settings
MemberView and triage issues only

Invite by email from /team. Invitees register or sign in with Google; the assigned role applies on join.

SymptomWhat happenedWhat to do
Admin tabs read-onlySession role is MemberOwner/Admin invites you again with Admin
403 on DSN/webhook routesrequire_min_role(Admin) rejectedSign in as Admin or Owner
Invitee lands as MemberInvite carried MemberPatch role from /team as Owner/Admin

What is on project settings?

Base path: /p/:projectId/settings

TabRouteWho
General/p/:projectId/settingsAdmin / Owner
DSN keys/p/:projectId/settings/dsnAdmin / Owner
Webhooks/p/:projectId/settings/webhooksAdmin / Owner

DSN keys

Create, rotate, revoke. Revoked keys → ingest 403 dsn_revoked. UI walkthrough: Projects.

Create a DSN key

command

curl -sS -b cookies.txt -X POST \
"http://localhost:8080/api/v1/projects/${PROJECT_ID}/dsn-keys" \
-H "Content-Type: application/json" \
-d '{"label":"local-dev"}'

Expected

HTTP/1.1 201 Created

{ "id": "<key-uuid>", "project_id": "<project-uuid>", "public_key": "a1b2…", "secret_key": "<shown once>", "label": "local-dev" }

Copy secret_key immediately. List responses omit it.

Webhooks

Destinations: Slack (format: "slack"), Discord (discord), or custom HTTPS (generic).

Event idWhen
issue_createdFirst event for a fingerprint
regressionResolved issue returns in a newer release

URLs must be public HTTPS. Private/loopback targets require EPURE_WEBHOOK_ALLOW_PRIVATE=1 (dev/tests only).

Create a generic webhook

command

curl -sS -b cookies.txt -X POST \
"http://localhost:8080/api/v1/webhooks" \
-H "Content-Type: application/json" \
-d '{"project_id":"550e8400-e29b-41d4-a716-446655440000","url":"https://hooks.example.com/epure","format":"generic","events":["issue_created","regression"]}'

Expected

HTTP/1.1 201 Created

{ "id": "<webhook-uuid>", "url": "https://hooks.example.com/epure", "format": "generic", "events": ["issue_created","regression"], "signing_secret": "<shown once>" }

Test fire: POST /api/v1/webhooks/{id}/test with the same cookie → { "sent": true }.

ResultWhat happenedWhat to do
400 on createURL failed SSRF checks, or bad format/eventsPublic HTTPS; format in slack/discord/generic; events issue_created or regression
Hook never arrivesPrivate URL blockedDo not set EPURE_WEBHOOK_ALLOW_PRIVATE=1 in prod

How does RLS apply for operators?

  • Dashboard never trusts a client-supplied org id; membership on the session sets app.current_org_id
  • Ingest does not use the app RLS role; it authenticates by DSN and writes for that project
  • Three URLs: DATABASE_URL (migrations), EPURE_INGEST_DATABASE_URL, EPURE_APP_DATABASE_URL; see Configuration

Checklist

  • At least one Owner exists
  • Production uses HTTPS + EPURE_SESSION_SECURE=1
  • DSN keys created; secrets stored outside git
  • CORS origins restricted (EPURE_CORS_ORIGINS) for browser SDKs
  • Webhooks pointed at public endpoints
  • Retention and ingest caps set per project
Why can a Member not rotate a DSN?

Rotate needs Admin or Owner; Members view and triage issues only. Ask an Owner to patch the role from /team. DSN create, rotate, and revoke sit on /p/:projectId/settings/dsn. Revoked keys then return 403 dsn_revoked on ingest.

Why is the Google button missing?

GOOGLE_CLIENT_ID is empty, so the login page hides Google. Set the id, secret, and a redirect URI matching {EPURE_PUBLIC_URL}/api/v1/auth/google/callback, then restart epure. Password login still works: Configuration.

Is a DSN the same as a session?

No. The DSN authenticates ingest only. Settings, Issues, and Events APIs use the login cookie and RLS. Sending DSN headers to /api/v1/* returns 401, and sending a cookie to envelope or store does nothing. See Authentication.

Next