@adatechnology/keycloak-admin 1.0.0-rc.1 → 1.0.0-rc.2
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 +6 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,9 +32,14 @@ const { id } = await keycloak.createUser({
|
|
|
32
32
|
})
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Operações: `createUser`, `findUserByEmail`, `listUsers`, `updateUser`, `setEnabled`,
|
|
35
|
+
Operações de usuário: `createUser`, `findUserByEmail`, `listUsers`, `updateUser`, `setEnabled`,
|
|
36
36
|
`updateAttributes`, `deleteUser`, `setPassword`, `setTemporaryPassword`.
|
|
37
37
|
|
|
38
|
+
Operações de grupo: `createGroup`, `updateGroup`, `deleteGroup`, `listGroups`, `addUserToGroup`,
|
|
39
|
+
`removeUserFromGroup`. Só o **primeiro nível** — grupo aninhado muda o significado de "pertencer",
|
|
40
|
+
porque quem está no filho herda o pai, e um produto que não modela hierarquia não deve criá-la por
|
|
41
|
+
acidente. A filiação é endereçada pelo **usuário** (`/users/{id}/groups/{id}`), não pelo grupo.
|
|
42
|
+
|
|
38
43
|
`listUsers({ first, limit, search })` devolve `{ users, hasMore }`. O realm não informa total, então
|
|
39
44
|
a página pede um registro a mais que o limite e descarta-o: é assim que `hasMore` sai sem uma
|
|
40
45
|
segunda chamada.
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,12 @@ type BuildKeycloakAdminEndpointsParams = {
|
|
|
16
16
|
readonly realm: string;
|
|
17
17
|
};
|
|
18
18
|
declare function buildKeycloakAdminEndpoints({ baseUrl, realm }: BuildKeycloakAdminEndpointsParams): {
|
|
19
|
+
readonly group: (groupId: string) => string;
|
|
20
|
+
readonly groups: string;
|
|
19
21
|
readonly token: `${string}/realms/${string}/protocol/openid-connect/token`;
|
|
20
22
|
readonly user: (userId: string) => string;
|
|
23
|
+
/** O Admin API endereça a filiação pelo usuário, não pelo grupo: é `PUT` para entrar, `DELETE` para sair. */
|
|
24
|
+
readonly userGroup: (userId: string, groupId: string) => string;
|
|
21
25
|
readonly userPassword: (userId: string) => string;
|
|
22
26
|
readonly users: string;
|
|
23
27
|
};
|
|
@@ -125,7 +129,51 @@ type SetTemporaryPasswordParams = {
|
|
|
125
129
|
readonly password: string;
|
|
126
130
|
readonly userId: string;
|
|
127
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* O grupo do realm. O Keycloak aceita hierarquia (`subGroups`), e este cliente trata só o primeiro
|
|
134
|
+
* nível de propósito: grupo aninhado muda o significado de "pertencer" — quem está no filho herda o
|
|
135
|
+
* pai —, e um produto que não modela hierarquia não deve criá-la por acidente.
|
|
136
|
+
*/
|
|
137
|
+
type KeycloakGroup = {
|
|
138
|
+
readonly attributes?: KeycloakUserAttributes;
|
|
139
|
+
readonly id: string;
|
|
140
|
+
readonly name: string;
|
|
141
|
+
readonly path?: string;
|
|
142
|
+
};
|
|
143
|
+
type CreateGroupParams = {
|
|
144
|
+
readonly attributes?: KeycloakUserAttributes;
|
|
145
|
+
readonly name: string;
|
|
146
|
+
};
|
|
147
|
+
type CreateGroupResult = {
|
|
148
|
+
readonly id: string;
|
|
149
|
+
};
|
|
150
|
+
type UpdateGroupParams = {
|
|
151
|
+
readonly groupId: string;
|
|
152
|
+
readonly group: Readonly<Partial<Pick<KeycloakGroup, 'attributes' | 'name'>>>;
|
|
153
|
+
};
|
|
154
|
+
type DeleteGroupParams = {
|
|
155
|
+
readonly groupId: string;
|
|
156
|
+
};
|
|
157
|
+
type ListGroupsParams = {
|
|
158
|
+
readonly first?: number;
|
|
159
|
+
readonly limit?: number;
|
|
160
|
+
readonly search?: string;
|
|
161
|
+
};
|
|
162
|
+
type ListGroupsResult = {
|
|
163
|
+
readonly groups: readonly KeycloakGroup[];
|
|
164
|
+
readonly hasMore: boolean;
|
|
165
|
+
};
|
|
166
|
+
type GroupMembershipParams = {
|
|
167
|
+
readonly groupId: string;
|
|
168
|
+
readonly userId: string;
|
|
169
|
+
};
|
|
128
170
|
type KeycloakAdminClient = {
|
|
171
|
+
addUserToGroup(params: GroupMembershipParams): Promise<void>;
|
|
172
|
+
createGroup(params: CreateGroupParams): Promise<CreateGroupResult>;
|
|
173
|
+
deleteGroup(params: DeleteGroupParams): Promise<void>;
|
|
174
|
+
listGroups(params?: ListGroupsParams): Promise<ListGroupsResult>;
|
|
175
|
+
removeUserFromGroup(params: GroupMembershipParams): Promise<void>;
|
|
176
|
+
updateGroup(params: UpdateGroupParams): Promise<void>;
|
|
129
177
|
createUser(params: CreateUserParams): Promise<CreateUserResult>;
|
|
130
178
|
deleteUser(params: DeleteUserParams): Promise<void>;
|
|
131
179
|
findUserByEmail(params: FindUserByEmailParams): Promise<KeycloakUser | undefined>;
|
|
@@ -155,4 +203,4 @@ declare function parseKeycloakAdminConfig(value: unknown): KeycloakAdminConfig;
|
|
|
155
203
|
|
|
156
204
|
declare function createKeycloakAdminClient({ config: rawConfig, fetch: injectedFetch, now, }: CreateKeycloakAdminClientParams): KeycloakAdminClient;
|
|
157
205
|
|
|
158
|
-
export { type CreateKeycloakAdminClientParams, type CreateUserParams, type CreateUserResult, type DeleteUserParams, type FetchLike, type FindUserByEmailParams, KEYCLOAK_ADMIN_ERROR_CODE, KEYCLOAK_ADMIN_TOKEN_RENEWAL_SKEW_MS, type KeycloakAdminClient, type KeycloakAdminConfig, type KeycloakAdminEndpoints, KeycloakAdminError, type KeycloakAdminErrorCode, type KeycloakAdminErrorContext, type KeycloakAdminErrorParams, type KeycloakPassword, type KeycloakUser, type KeycloakUserAttributes, type ListUsersParams, type ListUsersResult, type SerializedKeycloakAdminError, type SetEnabledParams, type SetPasswordParams, type SetTemporaryPasswordParams, type UpdateAttributesParams, type UpdateUserParams, buildKeycloakAdminEndpoints, createKeycloakAdminClient, isKeycloakAdminError, keycloakAdminConfigSchema, parseKeycloakAdminConfig };
|
|
206
|
+
export { type CreateGroupParams, type CreateGroupResult, type CreateKeycloakAdminClientParams, type CreateUserParams, type CreateUserResult, type DeleteGroupParams, type DeleteUserParams, type FetchLike, type FindUserByEmailParams, type GroupMembershipParams, KEYCLOAK_ADMIN_ERROR_CODE, KEYCLOAK_ADMIN_TOKEN_RENEWAL_SKEW_MS, type KeycloakAdminClient, type KeycloakAdminConfig, type KeycloakAdminEndpoints, KeycloakAdminError, type KeycloakAdminErrorCode, type KeycloakAdminErrorContext, type KeycloakAdminErrorParams, type KeycloakGroup, type KeycloakPassword, type KeycloakUser, type KeycloakUserAttributes, type ListGroupsParams, type ListGroupsResult, type ListUsersParams, type ListUsersResult, type SerializedKeycloakAdminError, type SetEnabledParams, type SetPasswordParams, type SetTemporaryPasswordParams, type UpdateAttributesParams, type UpdateGroupParams, type UpdateUserParams, buildKeycloakAdminEndpoints, createKeycloakAdminClient, isKeycloakAdminError, keycloakAdminConfigSchema, parseKeycloakAdminConfig };
|
package/dist/index.js
CHANGED
|
@@ -17,9 +17,14 @@ function buildKeycloakAdminEndpoints({ baseUrl, realm }) {
|
|
|
17
17
|
const origin = baseUrl.replace(/\/+$/, "");
|
|
18
18
|
const encodedRealm = encodeURIComponent(realm);
|
|
19
19
|
const users = `${origin}/admin/realms/${encodedRealm}/users`;
|
|
20
|
+
const groups = `${origin}/admin/realms/${encodedRealm}/groups`;
|
|
20
21
|
return {
|
|
22
|
+
group: (groupId) => `${groups}/${encodeURIComponent(groupId)}`,
|
|
23
|
+
groups,
|
|
21
24
|
token: `${origin}/realms/${encodedRealm}/protocol/openid-connect/token`,
|
|
22
25
|
user: (userId) => `${users}/${encodeURIComponent(userId)}`,
|
|
26
|
+
/** O Admin API endereça a filiação pelo usuário, não pelo grupo: é `PUT` para entrar, `DELETE` para sair. */
|
|
27
|
+
userGroup: (userId, groupId) => `${users}/${encodeURIComponent(userId)}/groups/${encodeURIComponent(groupId)}`,
|
|
23
28
|
userPassword: (userId) => `${users}/${encodeURIComponent(userId)}/reset-password`,
|
|
24
29
|
users
|
|
25
30
|
};
|
|
@@ -195,7 +200,7 @@ function normalizeAttributes(attributes) {
|
|
|
195
200
|
Object.entries(attributes).map(([key, value]) => [key, typeof value === "string" ? [value] : [...value]])
|
|
196
201
|
);
|
|
197
202
|
}
|
|
198
|
-
function
|
|
203
|
+
function idFromLocation(response) {
|
|
199
204
|
const location = response.headers.get("location");
|
|
200
205
|
const id = location?.split("/").filter(Boolean).at(-1);
|
|
201
206
|
if (id === void 0 || id === "") {
|
|
@@ -282,7 +287,7 @@ function createKeycloakAdminClient({
|
|
|
282
287
|
secrets: [password?.value],
|
|
283
288
|
url: endpoints.users
|
|
284
289
|
});
|
|
285
|
-
return { id:
|
|
290
|
+
return { id: idFromLocation(response) };
|
|
286
291
|
},
|
|
287
292
|
async deleteUser({ userId }) {
|
|
288
293
|
await adminRequest({ method: "DELETE", url: endpoints.user(userId) });
|
|
@@ -297,6 +302,41 @@ function createKeycloakAdminClient({
|
|
|
297
302
|
* Pede um a mais que o limite para saber se há próxima página sem uma segunda chamada — o
|
|
298
303
|
* Keycloak não devolve total, e contar o realm inteiro só para desenhar um botão é caro.
|
|
299
304
|
*/
|
|
305
|
+
async addUserToGroup({ groupId, userId }) {
|
|
306
|
+
await adminRequest({ method: "PUT", url: endpoints.userGroup(userId, groupId) });
|
|
307
|
+
},
|
|
308
|
+
async createGroup({ attributes, name }) {
|
|
309
|
+
const response = await adminRequest({
|
|
310
|
+
body: { ...attributes === void 0 ? {} : { attributes: normalizeAttributes(attributes) }, name },
|
|
311
|
+
method: "POST",
|
|
312
|
+
url: endpoints.groups
|
|
313
|
+
});
|
|
314
|
+
return { id: idFromLocation(response) };
|
|
315
|
+
},
|
|
316
|
+
async deleteGroup({ groupId }) {
|
|
317
|
+
await adminRequest({ method: "DELETE", url: endpoints.group(groupId) });
|
|
318
|
+
},
|
|
319
|
+
/** Mesmo recorte de `listUsers`: pede um a mais que o limite para saber se há próxima página. */
|
|
320
|
+
async listGroups({ first = 0, limit = 100, search } = {}) {
|
|
321
|
+
const query = new URLSearchParams({ first: String(first), max: String(limit + 1) });
|
|
322
|
+
if (search !== void 0 && search !== "") query.set("search", search);
|
|
323
|
+
const response = await adminRequest({ method: "GET", url: `${endpoints.groups}?${query}` });
|
|
324
|
+
const found = await response.json();
|
|
325
|
+
return { groups: found.slice(0, limit), hasMore: found.length > limit };
|
|
326
|
+
},
|
|
327
|
+
async removeUserFromGroup({ groupId, userId }) {
|
|
328
|
+
await adminRequest({ method: "DELETE", url: endpoints.userGroup(userId, groupId) });
|
|
329
|
+
},
|
|
330
|
+
async updateGroup({ group, groupId }) {
|
|
331
|
+
await adminRequest({
|
|
332
|
+
body: {
|
|
333
|
+
...group.attributes === void 0 ? {} : { attributes: normalizeAttributes(group.attributes) },
|
|
334
|
+
...group.name === void 0 ? {} : { name: group.name }
|
|
335
|
+
},
|
|
336
|
+
method: "PUT",
|
|
337
|
+
url: endpoints.group(groupId)
|
|
338
|
+
});
|
|
339
|
+
},
|
|
300
340
|
async listUsers({ first = 0, limit = 100, search } = {}) {
|
|
301
341
|
const query = new URLSearchParams({ first: String(first), max: String(limit + 1) });
|
|
302
342
|
if (search !== void 0 && search !== "") query.set("search", search);
|
package/package.json
CHANGED