Merge branch 'nice' into develop
This commit is contained in:
commit
7c1bcebcb8
2 changed files with 791 additions and 0 deletions
249
src/controllers/UserController.ts
Normal file
249
src/controllers/UserController.ts
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Path,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
import {
|
||||||
|
addUserGroup,
|
||||||
|
addUserRoles,
|
||||||
|
createGroup,
|
||||||
|
createUser,
|
||||||
|
deleteGroup,
|
||||||
|
deleteUser,
|
||||||
|
editUser,
|
||||||
|
getGroups,
|
||||||
|
getRoles,
|
||||||
|
getUser,
|
||||||
|
getUserGroups,
|
||||||
|
getUserList,
|
||||||
|
removeUserGroup,
|
||||||
|
removeUserRoles,
|
||||||
|
} from "../keycloak";
|
||||||
|
// import * as io from "../lib/websocket";
|
||||||
|
// import elasticsearch from "../elasticsearch";
|
||||||
|
// import { StorageFolder } from "../interfaces/storage-fs";
|
||||||
|
|
||||||
|
// if (!process.env.MINIO_BUCKET) throw Error("Default MinIO bucket must be specified.");
|
||||||
|
// if (!process.env.ELASTICSEARCH_INDEX) throw Error("Default ElasticSearch index must be specified.");
|
||||||
|
|
||||||
|
// const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
|
||||||
|
|
||||||
|
function stripLeadingSlash(str: string) {
|
||||||
|
return str.replace(/^\//, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("keycloak")
|
||||||
|
@Tags("Single-Sign On")
|
||||||
|
@Security("bearerAuth")
|
||||||
|
export class KeycloakController extends Controller {
|
||||||
|
@Get("user/{id}")
|
||||||
|
async getUser(@Path() id: string) {
|
||||||
|
return await getUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("user")
|
||||||
|
@Security("bearerAuth", ["system", "admin"])
|
||||||
|
async createUser(
|
||||||
|
@Request() request: { user: { sub: string; preferred_username: string } },
|
||||||
|
@Body()
|
||||||
|
body: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
email?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const userId = await createUser(body.username, body.password, {
|
||||||
|
firstName: body.firstName,
|
||||||
|
lastName: body.lastName,
|
||||||
|
email: body.email,
|
||||||
|
requiredActions: ["UPDATE_PASSWORD"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof userId !== "string") {
|
||||||
|
throw new Error("ไม่สามารถติดต่อกับระบบจัดการผู้ใช้งานได้");
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const folderData: any = {
|
||||||
|
pathname: stripLeadingSlash(`${body.username.trim()}/`),
|
||||||
|
path: "",
|
||||||
|
name: body.username.trim(),
|
||||||
|
hidden: false,
|
||||||
|
permissionGroup: [],
|
||||||
|
permissionUser: [],
|
||||||
|
permissionOther: {
|
||||||
|
create: false,
|
||||||
|
read: false,
|
||||||
|
update: false,
|
||||||
|
delete: false,
|
||||||
|
perm: false,
|
||||||
|
},
|
||||||
|
favourite: false,
|
||||||
|
color: "default",
|
||||||
|
type: "folder",
|
||||||
|
owner: body.username,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: now,
|
||||||
|
createdBy: request.user.preferred_username,
|
||||||
|
createdByUserId: request.user.sub,
|
||||||
|
updatedAt: now,
|
||||||
|
updatedBy: request.user.preferred_username,
|
||||||
|
updatedByUserId: request.user.sub,
|
||||||
|
};
|
||||||
|
|
||||||
|
// await elasticsearch.index({
|
||||||
|
// index: DEFAULT_INDEX!,
|
||||||
|
// document: folderData,
|
||||||
|
// refresh: "wait_for",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// io.getInstance()?.emit("FolderCreate", folderData);
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("user/{userId}")
|
||||||
|
async editUser(
|
||||||
|
@Path() userId: string,
|
||||||
|
@Body()
|
||||||
|
body: {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
email?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return await editUser(userId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("user/{userId}")
|
||||||
|
@Security("bearerAuth", ["system", "admin"])
|
||||||
|
async deleteUser(@Path() userId: string) {
|
||||||
|
return await deleteUser(userId).then(async (v) => {
|
||||||
|
if (!v) throw new Error("ไม่สามารถติดต่อกับระบบจัดการผู้ใช้งานได้");
|
||||||
|
// await elasticsearch.deleteByQuery({
|
||||||
|
// index: DEFAULT_INDEX,
|
||||||
|
// query: {
|
||||||
|
// bool: {
|
||||||
|
// must: [
|
||||||
|
// { prefix: { pathname: stripLeadingSlash(`${userId}/`) } },
|
||||||
|
// { match: { type: "folder" } },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// delete file that is not uploaded
|
||||||
|
// await elasticsearch.deleteByQuery({
|
||||||
|
// index: DEFAULT_INDEX,
|
||||||
|
// query: {
|
||||||
|
// bool: {
|
||||||
|
// must: [
|
||||||
|
// { prefix: { pathname: stripLeadingSlash(`${userId}/`) } },
|
||||||
|
// { match: { upload: false } },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// io.getInstance()?.emit("FolderDelete", { pathname: userId + "/" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("role")
|
||||||
|
async getRole() {
|
||||||
|
const role = await getRoles();
|
||||||
|
if (Array.isArray(role))
|
||||||
|
return role.filter(
|
||||||
|
(a) =>
|
||||||
|
!["uma_authorization", "offline_access", "default-roles"].some((b) => a.name.includes(b)),
|
||||||
|
);
|
||||||
|
throw new Error("Failed. Cannot get role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("{userId}/role")
|
||||||
|
async addRole(@Path() userId: string, @Body() body: { role: string[] }) {
|
||||||
|
const list = await getRoles();
|
||||||
|
|
||||||
|
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||||
|
|
||||||
|
const result = await addUserRoles(
|
||||||
|
userId,
|
||||||
|
list.filter((v) => body.role.includes(v.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result) throw new Error("Failed. Cannot set user's role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("{userId}/role/{roleId}")
|
||||||
|
async deleteRole(@Path() userId: string, @Path() roleId: string) {
|
||||||
|
const list = await getRoles();
|
||||||
|
|
||||||
|
if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server.");
|
||||||
|
|
||||||
|
const result = await removeUserRoles(
|
||||||
|
userId,
|
||||||
|
list.filter((v) => roleId === v.id),
|
||||||
|
);
|
||||||
|
if (!result) throw new Error("Failed. Cannot remove user's role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("user")
|
||||||
|
async getUserList(@Query() search = "") {
|
||||||
|
const result = await getUserList(search);
|
||||||
|
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new Error("Failed. Cannot get user list.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("group")
|
||||||
|
async getGroup() {
|
||||||
|
const group = await getGroups();
|
||||||
|
if (Array.isArray(group)) return group;
|
||||||
|
throw new Error("Failed. Cannot get group.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("group")
|
||||||
|
async createGroup(@Body() body: { name: string }) {
|
||||||
|
const result = await createGroup(body.name);
|
||||||
|
if (!result) throw new Error("Failed. Cannot create group.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("group/{groupId}")
|
||||||
|
async deleteGroup(@Path() groupId: string) {
|
||||||
|
const result = await deleteGroup(groupId);
|
||||||
|
if (!result) throw new Error("Failed. Cannot delete group.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("user/{userId}/group")
|
||||||
|
async getUserGroup(@Path() userId: string) {
|
||||||
|
const result = await getUserGroups(userId);
|
||||||
|
if (!result) throw new Error("Failed. Cannot list group to user.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("user/{userId}/group/{groupId}")
|
||||||
|
async addUserGroup(@Path() userId: string, @Path() groupId: string) {
|
||||||
|
const result = await addUserGroup(userId, groupId);
|
||||||
|
if (!result) throw new Error("Failed. Cannot assign group to user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("user/{userId}/group/{groupId}")
|
||||||
|
async removeUserGroup(@Path() userId: string, @Path() groupId: string) {
|
||||||
|
const result = await removeUserGroup(userId, groupId);
|
||||||
|
if (!result) throw new Error("Failed. Cannot remove group to user.");
|
||||||
|
}
|
||||||
|
}
|
||||||
542
src/keycloak/index.ts
Normal file
542
src/keycloak/index.ts
Normal file
|
|
@ -0,0 +1,542 @@
|
||||||
|
import { DecodedJwt, createDecoder } from "fast-jwt";
|
||||||
|
|
||||||
|
const KC_URL = process.env.KC_URL;
|
||||||
|
const KC_REALM = process.env.KC_REALM;
|
||||||
|
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
|
||||||
|
const KC_SECRET = process.env.KC_SERVICE_ACCOUNT_SECRET;
|
||||||
|
|
||||||
|
console.log(process.env.KC_URL);
|
||||||
|
|
||||||
|
let token: string | null = null;
|
||||||
|
let decoded: DecodedJwt | null = null;
|
||||||
|
|
||||||
|
const jwtDecode = createDecoder({ complete: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if token is expired or will expire in 30 seconds
|
||||||
|
* @returns true if expire or can't get exp, false otherwise
|
||||||
|
*/
|
||||||
|
export function isTokenExpired(token: string, beforeExpire: number = 30) {
|
||||||
|
decoded = jwtDecode(token);
|
||||||
|
|
||||||
|
if (decoded && decoded.payload.exp) {
|
||||||
|
return Date.now() / 1000 >= decoded.payload.exp - beforeExpire;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get token from keycloak if needed
|
||||||
|
*/
|
||||||
|
export async function getToken() {
|
||||||
|
if (!KC_CLIENT_ID || !KC_SECRET) {
|
||||||
|
throw new Error("KC_CLIENT_ID and KC_SECRET are required to used this feature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token && !isTokenExpired(token)) return token;
|
||||||
|
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
|
||||||
|
body.append("client_id", KC_CLIENT_ID);
|
||||||
|
body.append("client_secret", KC_SECRET);
|
||||||
|
body.append("grant_type", "client_credentials");
|
||||||
|
|
||||||
|
const res = await fetch(`${KC_URL}/realms/${KC_REALM}/protocol/openid-connect/token`, {
|
||||||
|
method: "POST",
|
||||||
|
body: body,
|
||||||
|
}).catch((e) => console.error(e));
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
throw new Error("Cannot get token from keycloak.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
|
if (data && data.access_token) {
|
||||||
|
token = data.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create keycloak user by given username and password with roles
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user
|
||||||
|
*
|
||||||
|
* @returns user uuid or true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function createUser(username: string, password: string, opts?: Record<string, any>) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: true,
|
||||||
|
credentials: [{ type: "password", value: password }],
|
||||||
|
username,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = res.headers.get("Location");
|
||||||
|
const id = path?.split("/").at(-1);
|
||||||
|
return id || true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get keycloak user by uuid
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user
|
||||||
|
*
|
||||||
|
* @returns user if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getUser(userId: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get keycloak user list
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user
|
||||||
|
*
|
||||||
|
* @returns user list if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getUserList(search = "") {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/users`.concat(!!search ? `?search=${search}` : ""),
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(res.json);
|
||||||
|
|
||||||
|
return ((await res.json()) as any[]).map((v: Record<string, string>) => ({
|
||||||
|
id: v.id,
|
||||||
|
username: v.username,
|
||||||
|
firstName: v.firstName,
|
||||||
|
lastName: v.lastName,
|
||||||
|
email: v.email,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update keycloak user by uuid
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user
|
||||||
|
*
|
||||||
|
* @returns user uuid or true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function editUser(userId: string, opts: Record<string, any>) {
|
||||||
|
const { password, ...rest } = opts;
|
||||||
|
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: true,
|
||||||
|
credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
|
||||||
|
...rest,
|
||||||
|
}),
|
||||||
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = res.headers.get("Location");
|
||||||
|
const id = path?.split("/").at(-1);
|
||||||
|
return id || true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete keycloak user by uuid
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user
|
||||||
|
*
|
||||||
|
* @returns user true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function deleteUser(userId: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
}).catch((e) => console.log("Keycloak Error: ", e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get roles list or specific role data
|
||||||
|
*
|
||||||
|
* Client must have permission to get realms roles
|
||||||
|
*
|
||||||
|
* @returns role's info (array if not specify name) if success, null if not found, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getRoles(name?: string) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/roles`.concat((name && `/${name}`) || ""),
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get roles list of user
|
||||||
|
*
|
||||||
|
* Client must have permission to get realms roles
|
||||||
|
*
|
||||||
|
* @returns role's info (array if not specify name) if success, null if not found, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getUserRoles(userId: string) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign role to user
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user roles
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function addUserRoles(userId: string, roles: { id: string; name: string }[]) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(roles),
|
||||||
|
},
|
||||||
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove role from user
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realm's user roles
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function removeUserRoles(userId: string, roles: { id: string; name: string }[]) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/role-mappings/realm`,
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
body: JSON.stringify(roles),
|
||||||
|
},
|
||||||
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get group list or specific group data
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realms group
|
||||||
|
*
|
||||||
|
* @returns group's info (array if not specify name) if success, null if not found, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getGroups(id?: string) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${KC_URL}/admin/realms/${KC_REALM}/groups`.concat((id && `/${id}`) || ""),
|
||||||
|
{
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create group
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realms group
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function createGroup(name: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ name }),
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit group
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realms group
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function editGroup(id: string, name: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups/${id}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
"content-type": `application/json`,
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({ name }),
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete group
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realms group
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function deleteGroup(id: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups/${id}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get group list or specific group data
|
||||||
|
*
|
||||||
|
* Client must have permission to manage realms group
|
||||||
|
*
|
||||||
|
* @returns group's info (array if not specify name) if success, null if not found, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function getUserGroups(userId?: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as any;
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add group to user
|
||||||
|
*
|
||||||
|
* Client must have permission to manage user group
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function addUserGroup(userId: string, groupId: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups/${groupId}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete group from user
|
||||||
|
*
|
||||||
|
* Client must have permission to manage user group
|
||||||
|
*
|
||||||
|
* @returns true if success, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function removeUserGroup(userId: string, groupId: string) {
|
||||||
|
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups/${groupId}`, {
|
||||||
|
// prettier-ignore
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${await getToken()}`,
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
}).catch((e) => console.log(e));
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
if (!res.ok && res.status !== 404) {
|
||||||
|
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue