Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/gateway-authority-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@etus/bhono": minor
"@etus/create-bhono": minor
---

Gateway-as-authority is now the default authorization model for scaffolded apps.

`@etus/auth` bumped to **0.8.0**: generated apps derive permissions from the gateway (RBAC ∪ access_grants) via `scopeMap`, keeping the BFF model (no user tokens stored). Admin is expressed as a scope (`bhono:admin → ['*']`) gated with `requirePermission` — the 0.8.0 release removed the Phase 2 role-derivation (a local role persisted from gateway scopes that could not be revoked) after an adversarial review, so authorization flows scope → permission and revocations propagate within the cache TTL.

Off by default so a freshly generated app boots before its gateway resource exists. Opt in per app with `ETUS_GATEWAY_AUTHORITY=true` + `ETUS_RESOURCE_ID` + `ETUS_INTEGRATION_KEY` after registering the app as a `web_app` resource in the gateway (see `docs/SETUP-GUIDE.md`).

Also: sessions moved to D1 (`createSqlSessionStore`, dev-login D1-only), the frontend migrated to the Seven design system (`@etus/seven-react`), and the CLI `--version` drift (stale `0.1.5`) was corrected.
8 changes: 8 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ ETUS_CLIENT_SECRET=your-client-secret-from-npx-etus-auth-init
ETUS_ALLOWED_DOMAINS=yourdomain.com,anotherdomain.com
ETUS_ADMIN_EMAILS=admin@yourdomain.com

# Gateway-as-authority (@etus/auth v0.7.0) — OFF by default. Turn on once your app
# is registered as a web_app resource in the gateway and you hold an integration
# key (see docs/SETUP-GUIDE.md → "Gateway-as-authority onboarding"). The key is a
# secret — keep it in .dev.vars locally and `wrangler secret put` in deployments.
# ETUS_GATEWAY_AUTHORITY=true
# ETUS_RESOURCE_ID=yourapp.example.com
# ETUS_INTEGRATION_KEY=ag_app_yourapp_xxx.yyy

# SendGrid
SENDGRID_API_KEY=your-sendgrid-api-key
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
Expand Down
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@ ETUS_CLIENT_SECRET=your-client-secret-from-npx-etus-auth-init
ETUS_ALLOWED_DOMAINS=yourdomain.com,anotherdomain.com
# Comma-separated emails that always receive the 'admin' role.
# @etus/auth requires at least one. The template ships a generic placeholder —
# replace it with your real product admin(s) before deploying.
# replace it with your real product admin(s) before deploying. With
# gateway-as-authority on, this is only the day-0 bootstrap — the real admin role
# is then granted by the gateway (see ETUS_GATEWAY_AUTHORITY below).
ETUS_ADMIN_EMAILS=admin@yourdomain.com

# Gateway-as-authority (@etus/auth v0.7.0) — OFF by default. Set to "true" once
# the app is registered as a web_app resource in the gateway and you have an
# integration key (scope app.grants.read). See docs/SETUP-GUIDE.md. The key is a
# secret — `wrangler secret put ETUS_INTEGRATION_KEY` in deployed environments.
# ETUS_GATEWAY_AUTHORITY=true
# ETUS_RESOURCE_ID=yourapp.example.com
# ETUS_INTEGRATION_KEY=ag_app_yourapp_xxx.yyy

# SendGrid (for invitations)
SENDGRID_API_KEY=your-sendgrid-api-key
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
Expand Down
8 changes: 6 additions & 2 deletions config/wrangler.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
"ETUS_GATEWAY": "https://ag.etus.io",
"ETUS_CLIENT_ID": "boilerplate-hono",
"ETUS_ALLOWED_DOMAINS": "brius.com.br,etus.com.br",
"ETUS_ADMIN_EMAILS": "aa@brius.com.br"
"ETUS_ADMIN_EMAILS": "aa@brius.com.br",
"ETUS_GATEWAY_AUTHORITY": "false",
"ETUS_RESOURCE_ID": ""
},
"env": {
"staging": {
Expand Down Expand Up @@ -74,7 +76,9 @@
"ETUS_GATEWAY": "https://ag.etus.io",
"ETUS_CLIENT_ID": "boilerplate-hono",
"ETUS_ALLOWED_DOMAINS": "brius.com.br,etus.com.br",
"ETUS_ADMIN_EMAILS": "aa@brius.com.br"
"ETUS_ADMIN_EMAILS": "aa@brius.com.br",
"ETUS_GATEWAY_AUTHORITY": "false",
"ETUS_RESOURCE_ID": ""
}
}
}
Expand Down
52 changes: 51 additions & 1 deletion docs/SETUP-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,57 @@ SENDGRID_FROM_EMAIL=noreply@example.com

**Importante:** `ETUS_ADMIN_EMAILS` deve ter pelo menos um email. Esses emails
recebem `role='admin'` no callback OAuth e conseguem acessar `/auth/admin/*`
e `/audit/logs`.
e `/audit/logs`. Com **gateway-as-authority** ligado (abaixo), esse allowlist
vira apenas o bootstrap do dia 0 — o papel admin real passa a vir do gateway.

---

### Gateway-as-authority (opcional, @etus/auth v0.7.0)

Por padrão o app decide as permissões localmente (`PERMISSIONS_MATRIX`). Para
tornar o **gateway a fonte de autoridade** — ou seja, derivar as permissões do
que o gateway resolveu para o usuário neste app (`RBAC ∪ access_grants`) —
ative `ETUS_GATEWAY_AUTHORITY`. O app continua BFF (não guarda tokens do
usuário); ele consulta o gateway com a **integration key do próprio app**.

Onboarding (uma vez, por app):

1. **Registrar o app como resource `web_app` no gateway** (um admin do gateway,
no console). Declare o vocabulário de scopes igual às **chaves do `SCOPE_MAP`**
em `src/server/auth/matrix.ts` (o template usa `bhono:admin|editor|viewer` —
renomeie para o prefixo do seu app). ⚠️ Há **duas** referências a esse
vocabulário: as chaves do `SCOPE_MAP` **e** `adminScopes` em
`src/server/auth/setup.ts` — renomeie as duas em lockstep, senão a promoção a
admin via gateway quebra silenciosamente.
2. **Provisionar uma integration key** bound a esse resource, com o scope
**`app.grants.read`**. Copie o segredo `ag_app_<slug>_…` (mostrado uma vez).
3. **Configurar o app:**
- `ETUS_GATEWAY_AUTHORITY=true`
- `ETUS_RESOURCE_ID=<slug do resource>` (var, em `config/wrangler.json`)
- `ETUS_INTEGRATION_KEY=<a key>` — **secret**:
`wrangler secret put ETUS_INTEGRATION_KEY --config config/wrangler.json`
em produção/staging. Nunca commitar.
4. **Conceder roles/grants aos usuários no gateway** que resolvam para `bhono:*`.
O app passa a ler isso no login (e revalida a cada `ttlSeconds`).

> **Dev local (importante):** `pnpm dev` usa o `@cloudflare/vite-plugin` com
> `configPath: ./config/wrangler.json`, que lê as variáveis de
> `config/wrangler.json` e os secrets de **`config/.dev.vars`** — NÃO do
> `.dev.vars` na raiz do projeto. Para testar gateway-as-authority localmente,
> ponha `ETUS_GATEWAY_AUTHORITY`/`ETUS_RESOURCE_ID` nas `vars` de
> `config/wrangler.json` e `ETUS_INTEGRATION_KEY` em `config/.dev.vars`
> (gitignored). Variáveis postas no `.dev.vars` da raiz são silenciosamente
> ignoradas (o app cai no default `ETUS_GATEWAY_AUTHORITY=false`).

Verificação: logar → o middleware popula `authPermissions` a partir do
`SCOPE_MAP` → `requirePermission(...)` autoriza. Se **todo** request der 403,
cheque que a key tem `app.grants.read` e que o vocabulário do resource bate com
as chaves do `SCOPE_MAP`. Disponibilidade: uma negação explícita do gateway
bloqueia; uma indisponibilidade transitória serve o cache (não derruba o app).

> O binding KV `SESSIONS` em `config/wrangler.json` não é mais usado para sessão
> (v0.6.0+ usa D1 via `createSqlSessionStore`); pode ser removido se nenhum outro
> código depender dele.

---

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@
"sync:template:check": "./scripts/sync-template.sh && git diff --exit-code packages/bhono-app/templates/"
},
"dependencies": {
"@etus/auth": "^0.5.0",
"@etus/auth": "^0.8.0",
"@etus/seven-react": "0.1.0-beta.3",
"@etus/tokens": "0.4.0-beta.2",
"@etus/ui": "0.4.0-beta.2",
"@hono/swagger-ui": "^0.6.1",
"@hono/zod-openapi": "^1.4.0",
"@hookform/resolvers": "^5.2.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/bhono-app/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function buildProgram(): Command {
return new Command()
.name('bhono-app')
.description('Create a new project from the Etus boilerplate')
.version('0.1.5')
.version('0.3.0')
.argument('<project-name>', 'Name of the project')
.option('-d, --domain <domain>', 'Production domain')
.option('-m, --modules <modules>', 'Comma-separated modules to include')
Expand Down
8 changes: 8 additions & 0 deletions packages/bhono-app/templates/base/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ ETUS_CLIENT_SECRET=your-client-secret-from-npx-etus-auth-init
ETUS_ALLOWED_DOMAINS=yourdomain.com,anotherdomain.com
ETUS_ADMIN_EMAILS=admin@yourdomain.com

# Gateway-as-authority (@etus/auth v0.7.0) — OFF by default. Turn on once your app
# is registered as a web_app resource in the gateway and you hold an integration
# key (see docs/SETUP-GUIDE.md → "Gateway-as-authority onboarding"). The key is a
# secret — keep it in .dev.vars locally and `wrangler secret put` in deployments.
# ETUS_GATEWAY_AUTHORITY=true
# ETUS_RESOURCE_ID=yourapp.example.com
# ETUS_INTEGRATION_KEY=ag_app_yourapp_xxx.yyy

# SendGrid
SENDGRID_API_KEY=your-sendgrid-api-key
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
Expand Down
12 changes: 11 additions & 1 deletion packages/bhono-app/templates/base/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@ ETUS_CLIENT_SECRET=your-client-secret-from-npx-etus-auth-init
ETUS_ALLOWED_DOMAINS=yourdomain.com,anotherdomain.com
# Comma-separated emails that always receive the 'admin' role.
# @etus/auth requires at least one. The template ships a generic placeholder —
# replace it with your real product admin(s) before deploying.
# replace it with your real product admin(s) before deploying. With
# gateway-as-authority on, this is only the day-0 bootstrap — the real admin role
# is then granted by the gateway (see ETUS_GATEWAY_AUTHORITY below).
ETUS_ADMIN_EMAILS=admin@yourdomain.com

# Gateway-as-authority (@etus/auth v0.7.0) — OFF by default. Set to "true" once
# the app is registered as a web_app resource in the gateway and you have an
# integration key (scope app.grants.read). See docs/SETUP-GUIDE.md. The key is a
# secret — `wrangler secret put ETUS_INTEGRATION_KEY` in deployed environments.
# ETUS_GATEWAY_AUTHORITY=true
# ETUS_RESOURCE_ID=yourapp.example.com
# ETUS_INTEGRATION_KEY=ag_app_yourapp_xxx.yyy

# SendGrid (for invitations)
SENDGRID_API_KEY=your-sendgrid-api-key
SENDGRID_FROM_EMAIL=noreply@yourdomain.com
Expand Down
8 changes: 6 additions & 2 deletions packages/bhono-app/templates/base/config/wrangler.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"ETUS_GATEWAY": "https://ag.etus.io",
"ETUS_CLIENT_ID": "boilerplate-hono",
"ETUS_ALLOWED_DOMAINS": "brius.com.br,etus.com.br",
"ETUS_ADMIN_EMAILS": "admin@etus.com.br"
"ETUS_ADMIN_EMAILS": "admin@etus.com.br",
"ETUS_GATEWAY_AUTHORITY": "false",
"ETUS_RESOURCE_ID": ""
},
"env": {
"staging": {
Expand Down Expand Up @@ -76,7 +78,9 @@
"ETUS_GATEWAY": "https://ag.etus.io",
"ETUS_CLIENT_ID": "boilerplate-hono",
"ETUS_ALLOWED_DOMAINS": "brius.com.br,etus.com.br",
"ETUS_ADMIN_EMAILS": "admin@etus.com.br"
"ETUS_ADMIN_EMAILS": "admin@etus.com.br",
"ETUS_GATEWAY_AUTHORITY": "false",
"ETUS_RESOURCE_ID": ""
}
}
}
Expand Down
52 changes: 51 additions & 1 deletion packages/bhono-app/templates/base/docs/SETUP-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,57 @@ SENDGRID_FROM_EMAIL=noreply@example.com

**Importante:** `ETUS_ADMIN_EMAILS` deve ter pelo menos um email. Esses emails
recebem `role='admin'` no callback OAuth e conseguem acessar `/auth/admin/*`
e `/audit/logs`.
e `/audit/logs`. Com **gateway-as-authority** ligado (abaixo), esse allowlist
vira apenas o bootstrap do dia 0 — o papel admin real passa a vir do gateway.

---

### Gateway-as-authority (opcional, @etus/auth v0.7.0)

Por padrão o app decide as permissões localmente (`PERMISSIONS_MATRIX`). Para
tornar o **gateway a fonte de autoridade** — ou seja, derivar as permissões do
que o gateway resolveu para o usuário neste app (`RBAC ∪ access_grants`) —
ative `ETUS_GATEWAY_AUTHORITY`. O app continua BFF (não guarda tokens do
usuário); ele consulta o gateway com a **integration key do próprio app**.

Onboarding (uma vez, por app):

1. **Registrar o app como resource `web_app` no gateway** (um admin do gateway,
no console). Declare o vocabulário de scopes igual às **chaves do `SCOPE_MAP`**
em `src/server/auth/matrix.ts` (o template usa `bhono:admin|editor|viewer` —
renomeie para o prefixo do seu app). ⚠️ Há **duas** referências a esse
vocabulário: as chaves do `SCOPE_MAP` **e** `adminScopes` em
`src/server/auth/setup.ts` — renomeie as duas em lockstep, senão a promoção a
admin via gateway quebra silenciosamente.
2. **Provisionar uma integration key** bound a esse resource, com o scope
**`app.grants.read`**. Copie o segredo `ag_app_<slug>_…` (mostrado uma vez).
3. **Configurar o app:**
- `ETUS_GATEWAY_AUTHORITY=true`
- `ETUS_RESOURCE_ID=<slug do resource>` (var, em `config/wrangler.json`)
- `ETUS_INTEGRATION_KEY=<a key>` — **secret**:
`wrangler secret put ETUS_INTEGRATION_KEY --config config/wrangler.json`
em produção/staging. Nunca commitar.
4. **Conceder roles/grants aos usuários no gateway** que resolvam para `bhono:*`.
O app passa a ler isso no login (e revalida a cada `ttlSeconds`).

> **Dev local (importante):** `pnpm dev` usa o `@cloudflare/vite-plugin` com
> `configPath: ./config/wrangler.json`, que lê as variáveis de
> `config/wrangler.json` e os secrets de **`config/.dev.vars`** — NÃO do
> `.dev.vars` na raiz do projeto. Para testar gateway-as-authority localmente,
> ponha `ETUS_GATEWAY_AUTHORITY`/`ETUS_RESOURCE_ID` nas `vars` de
> `config/wrangler.json` e `ETUS_INTEGRATION_KEY` em `config/.dev.vars`
> (gitignored). Variáveis postas no `.dev.vars` da raiz são silenciosamente
> ignoradas (o app cai no default `ETUS_GATEWAY_AUTHORITY=false`).

Verificação: logar → o middleware popula `authPermissions` a partir do
`SCOPE_MAP` → `requirePermission(...)` autoriza. Se **todo** request der 403,
cheque que a key tem `app.grants.read` e que o vocabulário do resource bate com
as chaves do `SCOPE_MAP`. Disponibilidade: uma negação explícita do gateway
bloqueia; uma indisponibilidade transitória serve o cache (não derruba o app).

> O binding KV `SESSIONS` em `config/wrangler.json` não é mais usado para sessão
> (v0.6.0+ usa D1 via `createSqlSessionStore`); pode ser removido se nenhum outro
> código depender dele.

---

Expand Down
5 changes: 4 additions & 1 deletion packages/bhono-app/templates/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
"sync:template:check": "./scripts/sync-template.sh && git diff --exit-code packages/bhono-app/templates/"
},
"dependencies": {
"@etus/auth": "^0.5.0",
"@etus/auth": "^0.8.0",
"@etus/seven-react": "0.1.0-beta.3",
"@etus/tokens": "0.4.0-beta.2",
"@etus/ui": "0.4.0-beta.2",
"@hono/swagger-ui": "^0.6.1",
"@hono/zod-openapi": "^1.4.0",
"@hookform/resolvers": "^5.2.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Link, useLocation } from '@tanstack/react-router'
import { useState, useEffect } from 'react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Avatar, AvatarFallback, AvatarImage, Button } from '@etus/seven-react'
import { Separator } from '@/components/ui/separator'
import { Icons } from '@/components/icons'
import { useAuth } from '@/hooks/use-auth'
Expand Down
101 changes: 0 additions & 101 deletions packages/bhono-app/templates/base/src/client/components/ui/avatar.tsx

This file was deleted.

Loading