@geekapps/auth-nextjs 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,196 +1,210 @@
1
- # @geekapps/auth-nextjs
2
-
3
- SDK Next.js para consumir o Geekapps Auth/IDP.
4
-
5
- ## Instalação
6
-
7
- ```bash
8
- bun add @geekapps/auth-nextjs
9
- ```
10
-
11
- ## Server
12
-
13
- ```ts
14
- // src/lib/auth.ts
15
- import { configureGeekappsAuth } from "@geekapps/auth-nextjs/server";
16
-
17
- export const auth = configureGeekappsAuth({
18
- issuer: process.env.GEEKAPPS_AUTH_ISSUER!,
19
- clientId: process.env.GEEKAPPS_AUTH_CLIENT_ID!,
20
- clientSecret: process.env.GEEKAPPS_AUTH_CLIENT_SECRET,
21
- redirectUri: process.env.GEEKAPPS_AUTH_REDIRECT_URI!,
22
- scopes: ["openid", "profile", "email"],
23
- });
24
- ```
25
-
26
- ```ts
27
- // app/api/auth/[...routes]/route.ts
28
- import { auth } from "@/lib/auth";
29
-
30
- export const GET = auth.handlers.GET;
31
- export const POST = auth.handlers.POST;
32
- export const PATCH = auth.handlers.PATCH;
33
- export const DELETE = auth.handlers.DELETE;
34
- ```
35
-
36
- ## Client
37
-
38
- ```tsx
39
- import { GeekappsAuthProvider } from "@geekapps/auth-nextjs/client";
40
-
41
- export function Providers({ children }: { children: React.ReactNode }) {
42
- return <GeekappsAuthProvider>{children}</GeekappsAuthProvider>;
43
- }
44
- ```
45
-
46
- Hooks principais:
47
-
48
- - `useAuth()`
49
- - `useOrg()` — organizações do próprio usuário (as que ele já é membro/dono)
50
- - `useAccounts()`
51
- - `useUsers()`
52
- - `useRoles()`
53
- - `usePermissions()`
54
- - `useSessions()`
55
- - `useMembers()` — membros e convites de uma organização
56
- - `usePublicOrganizations()` — descoberta de organizações públicas do app, separado de `useOrg()`
57
- - `useJoinRequests()` — solicitações de entrada em organizações públicas
58
- - `useInvites()` — aceite/recusa de convite por email neste próprio app
59
-
60
- ### Organizações: membros, convites e organizações públicas
61
-
62
- ```tsx
63
- import { useMembers } from "@geekapps/auth-nextjs/client";
64
-
65
- const { listMembers, addMember, updateMember, inviteMember, listInvites, revokeInvite } = useMembers();
66
-
67
- const members = await listMembers(org.id);
68
-
69
- // redirectUri é obrigatório — precisa estar cadastrado em Application.redirectUris,
70
- // e este próprio app implementa a rota que trata o ?invite_token= recebido lá.
71
- const result = await inviteMember(org.id, {
72
- email: "novo@empresa.com",
73
- roleId: "role-uuid",
74
- redirectUri: "https://meuapp.com/invites/accept",
75
- });
76
- ```
77
-
78
- ```tsx
79
- import { useInvites } from "@geekapps/auth-nextjs/client";
80
-
81
- // Na sua própria rota /invites/accept?invite_token=...
82
- const { getInvitePreview, acceptInvite, rejectInvite } = useInvites();
83
-
84
- const preview = await getInvitePreview(token); // funciona sem sessão
85
- if (!preview.alreadyAccepted && !preview.currentAccount) {
86
- // sem sessão — chame signIn() (de useAuth()) e volte pra essa rota depois
87
- }
88
-
89
- const result = await acceptInvite(token); // loginRequired:true se não houver sessão
90
- await rejectInvite(token); // não exige login
91
- ```
92
-
93
- ```tsx
94
- import { usePublicOrganizations, useJoinRequests } from "@geekapps/auth-nextjs/client";
95
-
96
- const { listPublicOrganizations } = usePublicOrganizations();
97
- const publicOrgs = await listPublicOrganizations();
98
-
99
- const { requestToJoin, getMyJoinRequest, listJoinRequests, approveJoinRequest } = useJoinRequests();
100
- await requestToJoin(org.id, { message: "Trabalho na região" });
101
- ```
102
-
103
- ## Organizações com CNPJ / Tax ID (`type: "legal"`)
104
-
105
- Toda organização tem um `type`: `"standard"` (padrão, sem documento) ou `"legal"` (pessoa jurídica, associada a um Tax ID). O Tax ID é único por aplicação e não muda depois da criação.
106
-
107
- - `taxIdType: "br_cnpj"` (padrão) — valida os dígitos verificadores (inclusive CNPJ alfanumérico) e **busca automaticamente** razão social, nome fantasia, contato e endereço na Receita (via BrasilAPI).
108
- - `taxIdType: "generic"` — Tax ID de outro país; só é normalizado, e `taxIdCountry` (ISO, ex.: `"US"`) é obrigatório.
109
-
110
- Obrigatórios de uma organização legal: `legalName` e `legalAddress.street`, `.city`, `.postalCode`, `.country`. O que a consulta não trouxer precisa ser informado pelo usuário.
111
-
112
- ```tsx
113
- // 1. Pré-preenche o formulário e descobre o que ainda falta pedir
114
- const { lookupTaxId, createOrganization } = useOrg();
115
- const result = await lookupTaxId("12.345.678/0001-95"); // type padrão: "br_cnpj"
116
- if (!result.success) throw new Error(result.error);
117
- const lookup = result.data;
118
- // lookup.data → { legalName, tradeName, legalEmail, legalPhone, legalAddress }
119
- // lookup.missingFields → ex.: ["legalAddress.postalCode"]
120
- // lookup.alreadyRegistered → já existe org com esse CNPJ neste app
121
-
122
- // 2. Cria — o servidor refaz a consulta e completa o que não foi enviado
123
- const org = await createOrganization({
124
- name: lookup.suggestedName ?? "Acme",
125
- slug: "acme",
126
- type: "legal",
127
- taxId: lookup.taxId,
128
- legalAddress: { postalCode: "01001000" }, // só o que faltou
129
- });
130
- // Se ainda faltar obrigatório: { success: false, code: "legal_fields_required", missingFields }.
131
-
132
- // Tax ID genérico (outro país)
133
- await createOrganization({
134
- name: "Acme LLC", slug: "acme-llc", type: "legal",
135
- taxId: "12-3456789", taxIdType: "generic", taxIdCountry: "US",
136
- legalName: "Acme LLC",
137
- legalAddress: { street: "1 Main St", city: "Austin", postalCode: "73301", country: "US" },
138
- });
139
- ```
140
-
141
- Com `@geekapps/auth-elements-nextjs` (0.3+), o `CreateOrganizationForm` (também usado no `OrgSwitcher` e no `ManageOrganizationModal`) já entrega essa UI pronta: alterna Padrão/Empresa, busca o CNPJ e pede só os obrigatórios que faltarem.
142
-
143
- ## Papel do usuário na organização
144
-
145
- `useOrg()` traz a participação do usuário logado na organização ativa, carregada sozinha ao trocar de organização:
146
-
147
- ```tsx
148
- const { currentMembership, getMyMembership, refreshMembership } = useOrg();
149
-
150
- currentMembership?.role?.key; // ex.: "admin"
151
- currentMembership?.isOwner; // dono passa em qualquer checagem
152
- currentMembership?.permissions.includes("org:update");
153
-
154
- const other = await getMyMembership(orgId); // outra organização
155
- ```
156
-
157
- Qualquer membro ativo pode listar os membros da organização com a role de cada um (`listMembers`). Editar, adicionar ou remover membros continua exigindo as permissões `org:members:*`.
158
-
159
- ## Standalone client auth actions
160
-
161
- `@geekapps/auth-nextjs/client` exporta funções standalone usadas pela `auth-screens`:
162
-
163
- ```ts
164
- import {
165
- loginWithPassword,
166
- registerWithPassword,
167
- requestPasswordlessLink,
168
- startGoogleLogin,
169
- } from "@geekapps/auth-nextjs/client";
170
- ```
171
-
172
- Depois de aplicar esse source, rode o build do package para gerar o `dist`:
173
-
174
- ```bash
175
- bun --filter @geekapps/auth-nextjs build
176
- ```
177
-
178
- ## Tipos das App Connections
179
-
180
- Em desenvolvimento (`NODE_ENV=development`), o SDK baixa o OpenAPI de cada app conectável e escreve os tipos em `geekapps-connections/`, ao subir e depois a cada minuto. O gerador vem embutido no próprio SDK; só é preciso ter o `openapi-typescript` como devDependency:
181
-
182
- ```bash
183
- npm i -D openapi-typescript
184
- ```
185
-
186
- Sem ele, a geração simplesmente não roda e o app funciona normalmente. Para desligar, use `GEEKAPPS_CONNECTIONS_TYPES=off`. Para checar no CI se os tipos estão em dia:
187
-
188
- ```ts
189
- import { generateConnectionTypes } from "@geekapps/auth-nextjs/typegen";
190
-
191
- const { stale } = await generateConnectionTypes({
192
- credentials: { issuer, clientId, clientSecret },
193
- check: true,
194
- });
195
- if (stale.length) process.exit(1);
196
- ```
1
+ # @geekapps/auth-nextjs
2
+
3
+ SDK Next.js para consumir o Geekapps Auth/IDP.
4
+
5
+ ## Instalação
6
+
7
+ ```bash
8
+ bun add @geekapps/auth-nextjs
9
+ ```
10
+
11
+ ## Server
12
+
13
+ ```ts
14
+ // src/lib/auth.ts
15
+ import { configureGeekappsAuth } from "@geekapps/auth-nextjs/server";
16
+
17
+ export const auth = configureGeekappsAuth({
18
+ issuer: process.env.GEEKAPPS_AUTH_ISSUER!,
19
+ clientId: process.env.GEEKAPPS_AUTH_CLIENT_ID!,
20
+ clientSecret: process.env.GEEKAPPS_AUTH_CLIENT_SECRET,
21
+ redirectUri: process.env.GEEKAPPS_AUTH_REDIRECT_URI!,
22
+ scopes: ["openid", "profile", "email"],
23
+ });
24
+ ```
25
+
26
+ ```ts
27
+ // app/api/auth/[...routes]/route.ts
28
+ import { auth } from "@/lib/auth";
29
+
30
+ export const GET = auth.handlers.GET;
31
+ export const POST = auth.handlers.POST;
32
+ export const PATCH = auth.handlers.PATCH;
33
+ export const DELETE = auth.handlers.DELETE;
34
+ ```
35
+
36
+ ## Client
37
+
38
+ ```tsx
39
+ import { GeekappsAuthProvider } from "@geekapps/auth-nextjs/client";
40
+
41
+ export function Providers({ children }: { children: React.ReactNode }) {
42
+ return <GeekappsAuthProvider>{children}</GeekappsAuthProvider>;
43
+ }
44
+ ```
45
+
46
+ Hooks principais:
47
+
48
+ - `useAuth()`
49
+ - `useOrg()` — organizações do próprio usuário (as que ele já é membro/dono)
50
+ - `useAccounts()`
51
+ - `useUsers()`
52
+ - `useRoles()`
53
+ - `usePermissions()`
54
+ - `useSessions()`
55
+ - `useMembers()` — membros e convites de uma organização
56
+ - `usePublicOrganizations()` — descoberta de organizações públicas do app, separado de `useOrg()`
57
+ - `useJoinRequests()` — solicitações de entrada em organizações públicas
58
+ - `useInvites()` — aceite/recusa de convite por email neste próprio app
59
+
60
+ ### Organizações: membros, convites e organizações públicas
61
+
62
+ ```tsx
63
+ import { useMembers } from "@geekapps/auth-nextjs/client";
64
+
65
+ const { listMembers, addMember, updateMember, inviteMember, listInvites, revokeInvite } = useMembers();
66
+
67
+ const members = await listMembers(org.id);
68
+
69
+ // redirectUri é obrigatório — precisa estar cadastrado em Application.redirectUris,
70
+ // e este próprio app implementa a rota que trata o ?invite_token= recebido lá.
71
+ const result = await inviteMember(org.id, {
72
+ email: "novo@empresa.com",
73
+ roleId: "role-uuid",
74
+ redirectUri: "https://meuapp.com/invites/accept",
75
+ });
76
+ ```
77
+
78
+ ```tsx
79
+ import { useInvites } from "@geekapps/auth-nextjs/client";
80
+
81
+ // Na sua própria rota /invites/accept?invite_token=...
82
+ const { getInvitePreview, acceptInvite, rejectInvite } = useInvites();
83
+
84
+ const preview = await getInvitePreview(token); // funciona sem sessão
85
+ if (!preview.alreadyAccepted && !preview.currentAccount) {
86
+ // sem sessão — chame signIn() (de useAuth()) e volte pra essa rota depois
87
+ }
88
+
89
+ const result = await acceptInvite(token); // loginRequired:true se não houver sessão
90
+ await rejectInvite(token); // não exige login
91
+ ```
92
+
93
+ ```tsx
94
+ import { usePublicOrganizations, useJoinRequests } from "@geekapps/auth-nextjs/client";
95
+
96
+ const { listPublicOrganizations } = usePublicOrganizations();
97
+ const publicOrgs = await listPublicOrganizations();
98
+
99
+ const { requestToJoin, getMyJoinRequest, listJoinRequests, approveJoinRequest } = useJoinRequests();
100
+ await requestToJoin(org.id, { message: "Trabalho na região" });
101
+ ```
102
+
103
+ ## Organizações com CNPJ / Tax ID (`type: "legal"`)
104
+
105
+ Toda organização tem um `type`: `"standard"` (padrão, sem documento) ou `"legal"` (pessoa jurídica, associada a um Tax ID). O Tax ID é único por aplicação e não muda depois da criação.
106
+
107
+ - `taxIdType: "br_cnpj"` (padrão) — valida os dígitos verificadores (inclusive CNPJ alfanumérico) e **busca automaticamente** razão social, nome fantasia, contato e endereço na Receita (via BrasilAPI).
108
+ - `taxIdType: "generic"` — Tax ID de outro país; só é normalizado, e `taxIdCountry` (ISO, ex.: `"US"`) é obrigatório.
109
+
110
+ Obrigatórios de uma organização legal: `legalName` e `legalAddress.street`, `.city`, `.postalCode`, `.country`. O que a consulta não trouxer precisa ser informado pelo usuário.
111
+
112
+ ```tsx
113
+ // 1. Pré-preenche o formulário e descobre o que ainda falta pedir
114
+ const { lookupTaxId, createOrganization } = useOrg();
115
+ const result = await lookupTaxId("12.345.678/0001-95"); // type padrão: "br_cnpj"
116
+ if (!result.success) throw new Error(result.error);
117
+ const lookup = result.data;
118
+ // lookup.data → { legalName, tradeName, legalEmail, legalPhone, legalAddress }
119
+ // lookup.missingFields → ex.: ["legalAddress.postalCode"]
120
+ // lookup.alreadyRegistered → já existe org com esse CNPJ neste app
121
+
122
+ // 2. Cria — o servidor refaz a consulta e completa o que não foi enviado
123
+ const org = await createOrganization({
124
+ name: lookup.suggestedName ?? "Acme",
125
+ slug: "acme",
126
+ type: "legal",
127
+ taxId: lookup.taxId,
128
+ legalAddress: { postalCode: "01001000" }, // só o que faltou
129
+ });
130
+ // Se ainda faltar obrigatório: { success: false, code: "legal_fields_required", missingFields }.
131
+
132
+ // Tax ID genérico (outro país)
133
+ await createOrganization({
134
+ name: "Acme LLC", slug: "acme-llc", type: "legal",
135
+ taxId: "12-3456789", taxIdType: "generic", taxIdCountry: "US",
136
+ legalName: "Acme LLC",
137
+ legalAddress: { street: "1 Main St", city: "Austin", postalCode: "73301", country: "US" },
138
+ });
139
+ ```
140
+
141
+ Com `@geekapps/auth-elements-nextjs` (0.3+), o `CreateOrganizationForm` (também usado no `OrgSwitcher` e no `ManageOrganizationModal`) já entrega essa UI pronta: alterna Padrão/Empresa, busca o CNPJ e pede só os obrigatórios que faltarem.
142
+
143
+ ## Papel do usuário na organização
144
+
145
+ `useOrg()` traz a participação do usuário logado na organização ativa, carregada sozinha ao trocar de organização:
146
+
147
+ ```tsx
148
+ const { currentMembership, getMyMembership, refreshMembership } = useOrg();
149
+
150
+ currentMembership?.role?.key; // ex.: "admin"
151
+ currentMembership?.isOwner; // dono passa em qualquer checagem
152
+ currentMembership?.permissions.includes("org:update");
153
+
154
+ const other = await getMyMembership(orgId); // outra organização
155
+ ```
156
+
157
+ Qualquer membro ativo pode listar os membros da organização com a role de cada um (`listMembers`). Editar, adicionar ou remover membros continua exigindo as permissões `org:members:*`.
158
+
159
+ ## Convites recebidos pelo usuário
160
+
161
+ Para mostrar os convites dentro do app, sem depender do link do e-mail:
162
+
163
+ ```tsx
164
+ const { listMyInvites, acceptInviteById, rejectInviteById } = useInvites();
165
+
166
+ const invites = await listMyInvites(); // pendentes para o e-mail da conta, só deste app
167
+ await acceptInviteById(invites[0].id); // cria a membership e atualiza as organizações
168
+ await rejectInviteById(invites[1].id);
169
+ ```
170
+
171
+ Só o próprio convidado (mesmo e-mail da conta) consegue aceitar ou recusar. Quem enviou o convite não recebe nenhum token.
172
+
173
+ ## Standalone client auth actions
174
+
175
+ `@geekapps/auth-nextjs/client` exporta funções standalone usadas pela `auth-screens`:
176
+
177
+ ```ts
178
+ import {
179
+ loginWithPassword,
180
+ registerWithPassword,
181
+ requestPasswordlessLink,
182
+ startGoogleLogin,
183
+ } from "@geekapps/auth-nextjs/client";
184
+ ```
185
+
186
+ Depois de aplicar esse source, rode o build do package para gerar o `dist`:
187
+
188
+ ```bash
189
+ bun --filter @geekapps/auth-nextjs build
190
+ ```
191
+
192
+ ## Tipos das App Connections
193
+
194
+ Em desenvolvimento (`NODE_ENV=development`), o SDK baixa o OpenAPI de cada app conectável e escreve os tipos em `geekapps-connections/`, ao subir e depois a cada minuto. O gerador vem embutido no próprio SDK; só é preciso ter o `openapi-typescript` como devDependency:
195
+
196
+ ```bash
197
+ npm i -D openapi-typescript
198
+ ```
199
+
200
+ Sem ele, a geração simplesmente não roda e o app funciona normalmente. Para desligar, use `GEEKAPPS_CONNECTIONS_TYPES=off`. Para checar no CI se os tipos estão em dia:
201
+
202
+ ```ts
203
+ import { generateConnectionTypes } from "@geekapps/auth-nextjs/typegen";
204
+
205
+ const { stale } = await generateConnectionTypes({
206
+ credentials: { issuer, clientId, clientSecret },
207
+ check: true,
208
+ });
209
+ if (stale.length) process.exit(1);
210
+ ```