mirror of https://github.com/boxyhq/jackson.git
383 lines
9.1 KiB
TypeScript
383 lines
9.1 KiB
TypeScript
import { randomUUID } from 'crypto';
|
|
|
|
import type {
|
|
Group,
|
|
DatabaseStore,
|
|
PaginationParams,
|
|
Response,
|
|
GroupMembership,
|
|
Records,
|
|
} from '../../typings';
|
|
import * as dbutils from '../../db/utils';
|
|
import { apiError, JacksonError } from '../../controller/error';
|
|
import { Base } from './Base';
|
|
import { indexNames } from './utils';
|
|
|
|
interface CreateGroupParams {
|
|
directoryId: string;
|
|
name: string;
|
|
raw: any;
|
|
id?: string;
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* parameters:
|
|
* groupId:
|
|
* name: groupId
|
|
* description: Group ID
|
|
* in: path
|
|
* required: true
|
|
* type: string
|
|
*/
|
|
|
|
/**
|
|
* @swagger
|
|
* definitions:
|
|
* Group:
|
|
* type: object
|
|
* properties:
|
|
* id:
|
|
* type: string
|
|
* description: Group ID
|
|
* name:
|
|
* type: string
|
|
* description: Group name
|
|
* raw:
|
|
* type: object
|
|
* description: Raw group attributes from the Identity Provider
|
|
*/
|
|
export class Groups extends Base {
|
|
constructor({ db }: { db: DatabaseStore }) {
|
|
super({ db });
|
|
}
|
|
|
|
// Create a new group
|
|
public async create(params: CreateGroupParams): Promise<Response<Group>> {
|
|
const { directoryId, name, raw, id: groupId } = params;
|
|
|
|
const id = groupId || randomUUID();
|
|
|
|
raw['id'] = id;
|
|
|
|
const group: Group = {
|
|
id,
|
|
name,
|
|
raw,
|
|
};
|
|
|
|
try {
|
|
await this.store('groups').put(
|
|
id,
|
|
group,
|
|
{
|
|
name: indexNames.directoryIdDisplayname,
|
|
value: dbutils.keyFromParts(directoryId, name),
|
|
},
|
|
{
|
|
name: indexNames.directoryId,
|
|
value: directoryId,
|
|
}
|
|
);
|
|
|
|
return { data: group, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/v1/dsync/groups/{groupId}:
|
|
* get:
|
|
* summary: Get group by id from a directory
|
|
* parameters:
|
|
* - $ref: '#/parameters/tenant'
|
|
* - $ref: '#/parameters/product'
|
|
* - $ref: '#/parameters/groupId'
|
|
* tags:
|
|
* - Directory Sync
|
|
* produces:
|
|
* - application/json
|
|
* responses:
|
|
* 200:
|
|
* description: Success
|
|
* schema:
|
|
* $ref: '#/definitions/Group'
|
|
*/
|
|
public async get(id: string): Promise<Response<Group>> {
|
|
try {
|
|
const group = await this.store('groups').get(id);
|
|
|
|
if (!group) {
|
|
throw new JacksonError(`Group with id ${id} not found.`, 404);
|
|
}
|
|
|
|
return { data: group, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
// Update the group data
|
|
public async update(
|
|
id: string,
|
|
param: {
|
|
name: string;
|
|
raw: any;
|
|
}
|
|
): Promise<Response<Group>> {
|
|
const { name, raw } = param;
|
|
|
|
const group: Group = {
|
|
id,
|
|
name,
|
|
raw,
|
|
};
|
|
|
|
try {
|
|
await this.store('groups').put(id, group);
|
|
|
|
return { data: group, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
// Delete a group by id
|
|
public async delete(id: string): Promise<Response<null>> {
|
|
try {
|
|
const { data, error } = await this.get(id);
|
|
|
|
if (error || !data) {
|
|
throw error;
|
|
}
|
|
|
|
await this.store('groups').delete(id);
|
|
await this.removeAllUsers(id);
|
|
|
|
return { data: null, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
// Add a user to a group
|
|
public async addUserToGroup(groupId: string, userId: string) {
|
|
const id = dbutils.keyDigest(dbutils.keyFromParts(groupId, userId));
|
|
|
|
await this.store('members').put(
|
|
id,
|
|
{
|
|
id: id,
|
|
group_id: groupId,
|
|
user_id: userId,
|
|
},
|
|
{
|
|
name: indexNames.groupId,
|
|
value: groupId,
|
|
}
|
|
);
|
|
}
|
|
|
|
// Remove a user from a group
|
|
public async removeUserFromGroup(groupId: string, userId: string) {
|
|
const id = dbutils.keyDigest(dbutils.keyFromParts(groupId, userId));
|
|
|
|
await this.store('members').delete(id);
|
|
}
|
|
|
|
// Check if a user is a member of a group
|
|
public async isUserInGroup(groupId: string, userId: string): Promise<boolean> {
|
|
const id = dbutils.keyDigest(dbutils.keyFromParts(groupId, userId));
|
|
|
|
return !!(await this.store('members').get(id));
|
|
}
|
|
|
|
// Search groups by displayName
|
|
public async search(displayName: string, directoryId: string): Promise<Response<Group[]>> {
|
|
try {
|
|
const { data: groups } = await this.store('groups').getByIndex({
|
|
name: indexNames.directoryIdDisplayname,
|
|
value: dbutils.keyFromParts(directoryId, displayName),
|
|
});
|
|
|
|
return { data: groups, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/v1/dsync/groups:
|
|
* get:
|
|
* summary: Get groups from a directory
|
|
* parameters:
|
|
* - $ref: '#/parameters/tenant'
|
|
* - $ref: '#/parameters/product'
|
|
* - $ref: '#/parameters/directoryId'
|
|
* - $ref: '#/parameters/pageOffset'
|
|
* - $ref: '#/parameters/pageLimit'
|
|
* - $ref: '#/parameters/pageToken'
|
|
* tags:
|
|
* - Directory Sync
|
|
* produces:
|
|
* - application/json
|
|
* responses:
|
|
* 200:
|
|
* description: Success
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/definitions/Group'
|
|
* pageToken:
|
|
* type: string
|
|
* description: token for pagination
|
|
*/
|
|
public async getAll(
|
|
params: PaginationParams & {
|
|
directoryId?: string;
|
|
}
|
|
): Promise<Response<Group[]>> {
|
|
const { pageOffset, pageLimit, pageToken, directoryId } = params;
|
|
|
|
try {
|
|
let result: Records;
|
|
|
|
// Filter by directoryId
|
|
if (directoryId) {
|
|
const index = {
|
|
name: indexNames.directoryId,
|
|
value: directoryId,
|
|
};
|
|
|
|
result = await this.store('groups').getByIndex(index, pageOffset, pageLimit, pageToken);
|
|
} else {
|
|
result = await this.store('groups').getAll(pageOffset, pageLimit, pageToken);
|
|
}
|
|
|
|
return { data: result.data, error: null, pageToken: result.pageToken };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* definitions:
|
|
* Member:
|
|
* type: object
|
|
* properties:
|
|
* user_id:
|
|
* type: string
|
|
* description: ID of the user
|
|
* /api/v1/dsync/groups/{groupId}/members:
|
|
* get:
|
|
* summary: Get list of members in a group
|
|
* parameters:
|
|
* - $ref: '#/parameters/tenant'
|
|
* - $ref: '#/parameters/product'
|
|
* - $ref: '#/parameters/groupId'
|
|
* - $ref: '#/parameters/directoryId'
|
|
* - $ref: '#/parameters/pageOffset'
|
|
* - $ref: '#/parameters/pageLimit'
|
|
* - $ref: '#/parameters/pageToken'
|
|
* tags:
|
|
* - Directory Sync
|
|
* produces:
|
|
* - application/json
|
|
* responses:
|
|
* 200:
|
|
* description: Success
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/definitions/Member'
|
|
*/
|
|
public async getGroupMembers(
|
|
parmas: { groupId: string } & PaginationParams
|
|
): Promise<Response<Pick<GroupMembership, 'user_id'>[]>> {
|
|
const { groupId, pageOffset, pageLimit } = parmas;
|
|
|
|
try {
|
|
const { data } = (await this.store('members').getByIndex(
|
|
{
|
|
name: indexNames.groupId,
|
|
value: groupId,
|
|
},
|
|
pageOffset,
|
|
pageLimit
|
|
)) as { data: GroupMembership[] };
|
|
|
|
const members = data.map((member) => {
|
|
return {
|
|
user_id: member.user_id,
|
|
};
|
|
});
|
|
|
|
return { data: members, error: null };
|
|
} catch (err: any) {
|
|
return apiError(err);
|
|
}
|
|
}
|
|
|
|
// Delete all groups from a directory
|
|
async deleteAll(directoryId: string) {
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
const { data: groups } = await this.store('groups').getByIndex(
|
|
{
|
|
name: indexNames.directoryId,
|
|
value: directoryId,
|
|
},
|
|
0,
|
|
this.bulkDeleteBatchSize
|
|
);
|
|
|
|
if (!groups || groups.length === 0) {
|
|
break;
|
|
}
|
|
|
|
const keys = groups.map((group) => group.id);
|
|
|
|
await this.store('groups').deleteMany(keys);
|
|
|
|
for (const key of keys) {
|
|
await this.removeAllUsers(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove all users from a group
|
|
public async removeAllUsers(groupId: string) {
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
const { data: members } = await this.store('members').getByIndex(
|
|
{
|
|
name: indexNames.groupId,
|
|
value: groupId,
|
|
},
|
|
0,
|
|
this.bulkDeleteBatchSize
|
|
);
|
|
|
|
if (!members || members.length === 0) {
|
|
break;
|
|
}
|
|
|
|
await this.store('members').deleteMany(members.map((member) => member.id));
|
|
}
|
|
}
|
|
}
|