Skip to content

Commit

Permalink
feat: security logs api (crowdin#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
yevheniyJ authored Feb 21, 2024
1 parent 212d50c commit 8427649
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { OrganizationWebhooks } from './organizationWebhooks';
import { ProjectsGroups } from './projectsGroups';
import { Reports } from './reports';
import { Screenshots } from './screenshots';
import { SecurityLogs } from './securityLogs';
import { SourceFiles } from './sourceFiles';
import { SourceStrings } from './sourceStrings';
import { StringComments } from './stringComments';
Expand Down Expand Up @@ -45,6 +46,7 @@ export * from './organizationWebhooks';
export * from './projectsGroups';
export * from './reports';
export * from './screenshots';
export * from './securityLogs';
export * from './sourceFiles';
export * from './sourceStrings';
export * from './stringComments';
Expand Down Expand Up @@ -96,6 +98,7 @@ export default class Client extends CrowdinApi {
readonly bundlesApi: Bundles;
readonly notificationsApi: Notifications;
readonly clientsApi: Clients;
readonly securityLogsApi: SecurityLogs;

constructor(credentials: Credentials, config?: ClientConfig) {
super(credentials, config);
Expand Down Expand Up @@ -128,5 +131,6 @@ export default class Client extends CrowdinApi {
this.bundlesApi = new Bundles(credentials, config);
this.notificationsApi = new Notifications(credentials, config);
this.clientsApi = new Clients(credentials, config);
this.securityLogsApi = new SecurityLogs(credentials, config);
}
}
102 changes: 102 additions & 0 deletions src/securityLogs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { CrowdinApi, PaginationOptions, ResponseList, ResponseObject } from '../core';

export class SecurityLogs extends CrowdinApi {
/**
* @param options optional parameters for the request
* @see https://support.crowdin.com/enterprise/api/#operation/api.projects.workflow-steps.getMany
*/
listOrganizationSecurityLogs(
options?: SecurityLogsModel.ListOrganizationSecurityLogsParams,
): Promise<ResponseList<SecurityLogsModel.SecurityLog>> {
let url = `${this.url}/security-logs`;
url = this.addQueryParam(url, 'event', options?.event);
url = this.addQueryParam(url, 'createdAfter', options?.createdAfter);
url = this.addQueryParam(url, 'createdBefore', options?.createdBefore);
url = this.addQueryParam(url, 'ipAddress', options?.ipAddress);
url = this.addQueryParam(url, 'userId', options?.userId);
return this.getList(url, options?.limit, options?.offset);
}

/**
* @param securityLogId security log identifier
* @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.security-logs.get
*/
getOrganizationSecurityLog(securityLogId: number): Promise<ResponseObject<SecurityLogsModel.SecurityLog>> {
const url = `${this.url}/security-logs/${securityLogId}`;
return this.get(url, this.defaultConfig());
}

/**
* @param userId user identifier
* @param options optional parameters for the request
* @see https://developer.crowdin.com/api/v2/#operation/api.users.security-logs.getMany
*/
listUserSecurityLogs(
userId: number,
options?: SecurityLogsModel.ListUserSecurityLogsParams,
): Promise<ResponseList<SecurityLogsModel.SecurityLog>> {
let url = `${this.url}/users/${userId}/security-logs`;
url = this.addQueryParam(url, 'event', options?.event);
url = this.addQueryParam(url, 'createdAfter', options?.createdAfter);
url = this.addQueryParam(url, 'createdBefore', options?.createdBefore);
url = this.addQueryParam(url, 'ipAddress', options?.ipAddress);
return this.getList(url, options?.limit, options?.offset);
}

/**
* @param userId security log identifier
* @param securityLogId security log identifier
* @see https://developer.crowdin.com/api/v2/#operation/api.users.security-logs.get
*/
getUserSecurityLog(userId: number, securityLogId: number): Promise<ResponseObject<SecurityLogsModel.SecurityLog>> {
const url = `${this.url}/users/${userId}/security-logs/${securityLogId}`;
return this.get(url, this.defaultConfig());
}
}

export namespace SecurityLogsModel {
export type Event =
| 'login'
| 'password.set'
| 'password.change'
| 'email.change'
| 'login.change'
| 'personal_token.issued'
| 'personal_token.revoked'
| 'mfa.enabled'
| 'mfa.disabled'
| 'session.revoke'
| 'session.revoke_all'
| 'sso.connect'
| 'sso.disconnect'
| 'user.remove'
| 'application.connected'
| 'application.disconnected'
| 'webauthn.created'
| 'webauthn.deleted'
| 'trusted_device.remove'
| 'trusted_device.remove_all'
| 'device_verification.enabled'
| 'device_verification.disabled';

export interface ListOrganizationSecurityLogsParams extends PaginationOptions {
event?: Event;
createdAfter?: string;
createdBefore?: string;
ipAddress?: string;
userId?: number;
}

export type ListUserSecurityLogsParams = Omit<ListOrganizationSecurityLogsParams, 'userId'>;

export interface SecurityLog {
id: number;
event: string;
info: string;
userId: number;
location: string;
ipAddress: string;
deviceName: string;
createdAt: string;
}
}
111 changes: 111 additions & 0 deletions tests/securityLogs/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as nock from 'nock';
import { Credentials, SecurityLogs } from '../../src';

describe('SecurityLogs API', () => {
let scope: nock.Scope;
const credentials: Credentials = {
token: 'testToken',
organization: 'testOrg',
};
const api: SecurityLogs = new SecurityLogs(credentials);
const userId = 2;
const securityLogId = 4;

const limit = 25;

beforeAll(() => {
scope = nock(api.url)
.get('/security-logs', undefined, {
reqheaders: {
Authorization: `Bearer ${api.token}`,
},
})
.reply(200, {
data: [
{
data: {
id: securityLogId,
userId,
},
},
],
pagination: {
offset: 0,
limit: limit,
},
})
.get(`/security-logs/${securityLogId}`, undefined, {
reqheaders: {
Authorization: `Bearer ${api.token}`,
},
})
.reply(200, {
data: {
id: securityLogId,
userId,
},
})
.get(`/users/${userId}/security-logs`, undefined, {
reqheaders: {
Authorization: `Bearer ${api.token}`,
},
})
.reply(200, {
data: [
{
data: {
id: securityLogId,
userId,
},
},
],
pagination: {
offset: 0,
limit: limit,
},
})
.get(`/users/${userId}/security-logs/${securityLogId}`, undefined, {
reqheaders: {
Authorization: `Bearer ${api.token}`,
},
})
.reply(200, {
data: {
id: securityLogId,
userId,
},
});
});

afterAll(() => {
scope.done();
});

it('List Organization Security Logs', async () => {
const logs = await api.listOrganizationSecurityLogs();
expect(logs.data.length).toBe(1);
expect(logs.data[0].data.id).toBe(securityLogId);
expect(logs.data[0].data.userId).toBe(userId);
expect(logs.pagination.limit).toBe(limit);
});

it('Get Organization Security Log', async () => {
const log = await api.getOrganizationSecurityLog(securityLogId);
expect(log.data.id).toBe(securityLogId);
expect(log.data.userId).toBe(userId);
});

it('List User Security Logs', async () => {
const logs = await api.listUserSecurityLogs(userId);
expect(logs.data.length).toBe(1);
expect(logs.data[0].data.id).toBe(securityLogId);
expect(logs.data[0].data.userId).toBe(userId);
expect(logs.pagination.limit).toBe(limit);
});

it('Get User Security Log', async () => {
const log = await api.getUserSecurityLog(userId, securityLogId);
expect(log.data.id).toBe(securityLogId);
expect(log.data.userId).toBe(userId);
});
});

0 comments on commit 8427649

Please sign in to comment.