@geekapps/auth-nextjs 0.17.0 → 0.20.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
@@ -100,6 +100,46 @@ const { requestToJoin, getMyJoinRequest, listJoinRequests, approveJoinRequest }
100
100
  await requestToJoin(org.id, { message: "Trabalho na região" });
101
101
  ```
102
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
+
103
143
  ## Standalone client auth actions
104
144
 
105
145
  `@geekapps/auth-nextjs/client` exporta funções standalone usadas pela `auth-screens`:
@@ -118,3 +158,23 @@ Depois de aplicar esse source, rode o build do package para gerar o `dist`:
118
158
  ```bash
119
159
  bun --filter @geekapps/auth-nextjs build
120
160
  ```
161
+
162
+ ## Tipos das App Connections
163
+
164
+ 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:
165
+
166
+ ```bash
167
+ npm i -D openapi-typescript
168
+ ```
169
+
170
+ 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:
171
+
172
+ ```ts
173
+ import { generateConnectionTypes } from "@geekapps/auth-nextjs/typegen";
174
+
175
+ const { stale } = await generateConnectionTypes({
176
+ credentials: { issuer, clientId, clientSecret },
177
+ check: true,
178
+ });
179
+ if (stale.length) process.exit(1);
180
+ ```
package/dist/client.d.ts CHANGED
@@ -18,6 +18,54 @@ type AuthResult<T = unknown> = {
18
18
  success: false;
19
19
  error: string;
20
20
  status?: number;
21
+ /** Código de erro da API (ex.: "legal_fields_required", "invalid_tax_id"). */
22
+ code?: string;
23
+ /** Campos obrigatórios que faltaram (ex.: "legalName", "legalAddress.city"). */
24
+ missingFields?: string[];
25
+ };
26
+ /** Erro de request com o corpo estruturado da API (code/missingFields). */
27
+ declare class GeekappsApiError extends Error {
28
+ readonly status: number;
29
+ readonly code?: string | undefined;
30
+ readonly missingFields?: string[] | undefined;
31
+ constructor(message: string, status: number, code?: string | undefined, missingFields?: string[] | undefined);
32
+ }
33
+ /** standard: organização comum. legal: pessoa jurídica associada a um Tax ID (CNPJ ou genérico). */
34
+ type OrganizationType = "standard" | "legal";
35
+ type TaxIdType = "br_cnpj" | "generic";
36
+ type OrganizationLegalAddress = {
37
+ street?: string | null;
38
+ number?: string | null;
39
+ complement?: string | null;
40
+ district?: string | null;
41
+ city?: string | null;
42
+ state?: string | null;
43
+ postalCode?: string | null;
44
+ /** ISO 3166-1 alpha-2 */
45
+ country?: string | null;
46
+ };
47
+ type OrganizationLegalData = {
48
+ legalName: string | null;
49
+ tradeName: string | null;
50
+ legalEmail: string | null;
51
+ legalPhone: string | null;
52
+ legalAddress: OrganizationLegalAddress | null;
53
+ };
54
+ type TaxIdLookupResult = {
55
+ taxId: string;
56
+ taxIdType: TaxIdType;
57
+ taxIdCountry: string | null;
58
+ valid: boolean;
59
+ /** Já existe uma organização deste app com esse Tax ID. */
60
+ alreadyRegistered: boolean;
61
+ /** false quando a fonte pública estava indisponível — preencha manualmente. */
62
+ lookupAvailable: boolean;
63
+ found: boolean;
64
+ registrationStatus: string | null;
65
+ suggestedName: string | null;
66
+ data: OrganizationLegalData;
67
+ /** Obrigatórios que a consulta não conseguiu preencher — peça ao usuário. */
68
+ missingFields: string[];
21
69
  };
22
70
  type User = {
23
71
  id: string;
@@ -227,6 +275,16 @@ type Organization = {
227
275
  status: OrganizationStatus;
228
276
  /** "private" (padrão) nunca sai em listPublicOrganizations(); "public" sai, e aceita join requests. */
229
277
  visibility?: "public" | "private";
278
+ type?: OrganizationType;
279
+ taxId?: string | null;
280
+ taxIdType?: TaxIdType | null;
281
+ taxIdCountry?: string | null;
282
+ legalName?: string | null;
283
+ tradeName?: string | null;
284
+ legalEmail?: string | null;
285
+ legalPhone?: string | null;
286
+ legalAddress?: OrganizationLegalAddress | null;
287
+ taxIdVerifiedAt?: string | Date | null;
230
288
  members?: OrganizationMember[];
231
289
  ownedApplications?: OrganizationOwnedApplication[];
232
290
  applicationLinks?: Array<{
@@ -280,8 +338,16 @@ type OrganizationInput = {
280
338
  domain?: string | null;
281
339
  imageUrl?: string | null;
282
340
  faviconUrl?: string | null;
283
- };
284
- type OrganizationUpdateInput = Partial<OrganizationInput> & {
341
+ visibility?: "public" | "private";
342
+ /** "legal" exige taxId; dados da empresa (CNPJ) são buscados automaticamente no servidor. */
343
+ type?: OrganizationType;
344
+ taxId?: string | null;
345
+ /** Padrão: "br_cnpj". */
346
+ taxIdType?: TaxIdType | null;
347
+ /** Obrigatório para taxIdType "generic". */
348
+ taxIdCountry?: string | null;
349
+ } & Partial<OrganizationLegalData>;
350
+ type OrganizationUpdateInput = Partial<Omit<OrganizationInput, "type" | "taxId" | "taxIdType" | "taxIdCountry">> & {
285
351
  status?: OrganizationStatus;
286
352
  };
287
353
  type RoleInput = {
@@ -451,6 +517,8 @@ type GeekappsAuthContextValue = {
451
517
  refreshOrganizations: () => Promise<void>;
452
518
  setCurrentOrg: (organizationId: string | null) => Promise<void>;
453
519
  createOrganization: (input: OrganizationInput) => Promise<AuthResult<Organization>>;
520
+ /** Valida o Tax ID e busca os dados da empresa (CNPJ) para pré-preencher a criação. */
521
+ lookupTaxId: (value: string, type?: TaxIdType) => Promise<AuthResult<TaxIdLookupResult>>;
454
522
  updateOrganization: (id: string, input: OrganizationUpdateInput) => Promise<AuthResult<Organization>>;
455
523
  deleteOrganization: (id: string) => Promise<AuthResult<null>>;
456
524
  hasPermission: (options: PermissionCheckOptions) => Promise<boolean>;
@@ -523,7 +591,7 @@ type GeekappsAuthContextValue = {
523
591
  listInvites: (organizationId: string) => Promise<OrganizationInvite[]>;
524
592
  revokeInvite: (organizationId: string, inviteId: string) => Promise<AuthResult<null>>;
525
593
  };
526
- type UseOrgResult = Pick<GeekappsAuthContextValue, "organizations" | "currentOrg" | "currentOrgId" | "isLoadingOrganizations" | "refreshOrganizations" | "setCurrentOrg" | "createOrganization" | "updateOrganization" | "deleteOrganization" | "hasPermission">;
594
+ type UseOrgResult = Pick<GeekappsAuthContextValue, "organizations" | "currentOrg" | "currentOrgId" | "isLoadingOrganizations" | "refreshOrganizations" | "setCurrentOrg" | "createOrganization" | "lookupTaxId" | "updateOrganization" | "deleteOrganization" | "hasPermission">;
527
595
  type UseAccountsResult = Pick<GeekappsAuthContextValue, "accounts" | "addAccount" | "isLoadingAccounts" | "refreshAccounts" | "switchAccount">;
528
596
  type UseUsersResult = Pick<GeekappsAuthContextValue, "listUsers" | "getUser" | "createUser" | "updateUser" | "deleteUser">;
529
597
  type UseRolesResult = Pick<GeekappsAuthContextValue, "listRoles" | "getRole" | "createRole" | "updateRole" | "deleteRole">;
@@ -607,4 +675,4 @@ type UseServiceAccountResult = {
607
675
  };
608
676
  declare function useServiceAccount(): UseServiceAccountResult;
609
677
 
610
- export { type AcceptInviteResult, type AddMemberInput, type AuthResult, type CallbackUrlOptions, type ConnectedAccount, type CreateServiceAccountInput, type GeekappsAuthContextValue, GeekappsAuthProvider, type GeekappsAuthProviderProps, GeekappsSessionExpiredError, type JoinRequestStatus, type LoginResponse, type MemberInviteInput, type OAuthClientType, type OidcPrompt, type Organization, type OrganizationInput, type OrganizationInvite, type OrganizationInvitePreview, type OrganizationJoinRequest, type OrganizationMember, type OrganizationOwnedApplication, type OrganizationStatus, type OrganizationUpdateInput, type Permission, type PermissionCheckOptions, type PermissionInput, type PermissionUpdateInput, type PublicOrganization, type RegisterResponse, type Role, type RoleAssignment, type RoleInput, type RolePermission, type RoleUpdateInput, type ServiceAccount, type ServiceAccountWithSecret, type SessionApplication, type SignInInput, type SignInOptions, type UpdateMemberInput, type UpdateMemberRoleInput, type UseAccountsResult, type UseInvitesResult, type UseJoinRequestsResult, type UseMembersResult, type UseOrgResult, type UsePermissionsResult, type UsePublicOrganizationsResult, type UseRolesResult, type UseServiceAccountResult, type UseSessionsResult, type UseUsersResult, type User, type UserInput, type UserSession, type UserStatus, loginWithPassword, registerWithPassword, requestPasswordlessLink, startGoogleLogin, useAccounts, useAuth, useInvites, useJoinRequests, useMembers, useOrg, usePermissions, usePublicOrganizations, useRoles, useServiceAccount, useSessions, useUsers };
678
+ export { type AcceptInviteResult, type AddMemberInput, type AuthResult, type CallbackUrlOptions, type ConnectedAccount, type CreateServiceAccountInput, GeekappsApiError, type GeekappsAuthContextValue, GeekappsAuthProvider, type GeekappsAuthProviderProps, GeekappsSessionExpiredError, type JoinRequestStatus, type LoginResponse, type MemberInviteInput, type OAuthClientType, type OidcPrompt, type Organization, type OrganizationInput, type OrganizationInvite, type OrganizationInvitePreview, type OrganizationJoinRequest, type OrganizationLegalAddress, type OrganizationLegalData, type OrganizationMember, type OrganizationOwnedApplication, type OrganizationStatus, type OrganizationType, type OrganizationUpdateInput, type Permission, type PermissionCheckOptions, type PermissionInput, type PermissionUpdateInput, type PublicOrganization, type RegisterResponse, type Role, type RoleAssignment, type RoleInput, type RolePermission, type RoleUpdateInput, type ServiceAccount, type ServiceAccountWithSecret, type SessionApplication, type SignInInput, type SignInOptions, type TaxIdLookupResult, type TaxIdType, type UpdateMemberInput, type UpdateMemberRoleInput, type UseAccountsResult, type UseInvitesResult, type UseJoinRequestsResult, type UseMembersResult, type UseOrgResult, type UsePermissionsResult, type UsePublicOrganizationsResult, type UseRolesResult, type UseServiceAccountResult, type UseSessionsResult, type UseUsersResult, type User, type UserInput, type UserSession, type UserStatus, loginWithPassword, registerWithPassword, requestPasswordlessLink, startGoogleLogin, useAccounts, useAuth, useInvites, useJoinRequests, useMembers, useOrg, usePermissions, usePublicOrganizations, useRoles, useServiceAccount, useSessions, useUsers };
package/dist/client.js CHANGED
@@ -18,6 +18,18 @@ var GeekappsSessionExpiredError = class extends Error {
18
18
  this.name = "GeekappsSessionExpiredError";
19
19
  }
20
20
  };
21
+ var GeekappsApiError = class extends Error {
22
+ constructor(message, status, code, missingFields) {
23
+ super(message);
24
+ this.status = status;
25
+ this.code = code;
26
+ this.missingFields = missingFields;
27
+ this.name = "GeekappsApiError";
28
+ }
29
+ status;
30
+ code;
31
+ missingFields;
32
+ };
21
33
  function normalizeBasePath(basePath) {
22
34
  return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
23
35
  }
@@ -164,7 +176,13 @@ function GeekappsAuthProvider({
164
176
  throw new GeekappsSessionExpiredError();
165
177
  }
166
178
  if (!response.ok) {
167
- throw new Error(await parseError(response));
179
+ const data = await response.clone().json().catch(() => null);
180
+ throw new GeekappsApiError(
181
+ await parseError(response),
182
+ response.status,
183
+ typeof data?.code === "string" ? data.code : void 0,
184
+ Array.isArray(data?.missingFields) ? data.missingFields : void 0
185
+ );
168
186
  }
169
187
  if (response.status === 204) {
170
188
  return null;
@@ -291,6 +309,15 @@ function GeekappsAuthProvider({
291
309
  try {
292
310
  return { success: true, data: await fn() };
293
311
  } catch (error) {
312
+ if (error instanceof GeekappsApiError) {
313
+ return {
314
+ success: false,
315
+ error: error.message,
316
+ status: error.status,
317
+ code: error.code,
318
+ missingFields: error.missingFields
319
+ };
320
+ }
294
321
  return { success: false, error: error instanceof Error ? error.message : "Unexpected error" };
295
322
  }
296
323
  }
@@ -409,6 +436,11 @@ function GeekappsAuthProvider({
409
436
  setCurrentOrgId(organization.id);
410
437
  return organization;
411
438
  }),
439
+ lookupTaxId: (value2, type = "br_cnpj") => toAuthResult(
440
+ () => request(
441
+ `/organizations/tax-id/lookup?type=${encodeURIComponent(type)}&value=${encodeURIComponent(value2)}`
442
+ )
443
+ ),
412
444
  updateOrganization: (id, input) => toAuthResult(async () => {
413
445
  const organization = await request(`/organizations/${id}`, { method: "PATCH", body: input });
414
446
  await refreshOrganizations();
@@ -626,6 +658,7 @@ function useOrg() {
626
658
  refreshOrganizations: context.refreshOrganizations,
627
659
  setCurrentOrg: context.setCurrentOrg,
628
660
  createOrganization: context.createOrganization,
661
+ lookupTaxId: context.lookupTaxId,
629
662
  updateOrganization: context.updateOrganization,
630
663
  deleteOrganization: context.deleteOrganization,
631
664
  hasPermission: context.hasPermission
@@ -764,6 +797,7 @@ function useServiceAccount() {
764
797
  };
765
798
  }
766
799
  export {
800
+ GeekappsApiError,
767
801
  GeekappsAuthProvider,
768
802
  GeekappsSessionExpiredError,
769
803
  loginWithPassword,