Compare commits

...

31 Commits

Author SHA1 Message Date
Rıdvan Akca
8e45a42a6b feat(discord): add change scheduled event action 2024-02-23 18:09:54 +03:00
Ömer Faruk Aydın
9a7cdf42e1 Merge pull request #1641 from automatisch/remove-role-id-from-user-serializer
chore: Remove redundant roleId from user serializer
2024-02-23 11:28:20 +01:00
Ömer Faruk Aydın
c36b652d5b Merge pull request #1640 from automatisch/rest-get-notifications
feat: Implement get notifications API endpoint
2024-02-23 11:28:11 +01:00
Ömer Faruk Aydın
553070fc23 Merge pull request #1638 from automatisch/rest-get-payment-paddle-info
feat: Implement get paddle info API endpoint
2024-02-23 11:27:50 +01:00
Ömer Faruk Aydın
5d69f7e24f Merge pull request #1637 from automatisch/rest-get-payment-plans
feat: Implement get payment plans API endpoint
2024-02-23 11:27:36 +01:00
Ömer Faruk Aydın
bc0e2bada0 Merge pull request #1635 from automatisch/rest-get-role
feat: Implement get role API endpoint for admin
2024-02-23 11:27:22 +01:00
Ömer Faruk Aydın
80b6cc1d94 Merge pull request #1636 from automatisch/rest-get-permissions-catalog
feat: Implement permission catalog API endpoint
2024-02-23 11:20:34 +01:00
Ömer Faruk Aydın
bce3273e64 Merge pull request #1634 from automatisch/rest-get-roles
feat: Implement admin get roles API endpoint
2024-02-23 11:20:25 +01:00
Faruk AYDIN
3abf61152a chore: Remove redundant roleId from user serializer 2024-02-23 01:30:57 +01:00
Faruk AYDIN
14923d4cd6 feat: Implement get notifications API endpoint 2024-02-23 01:24:56 +01:00
Faruk AYDIN
6fdc4bf900 feat: Implement get paddle info API endpoint 2024-02-22 20:22:05 +01:00
Faruk AYDIN
d21e1f75b5 feat: Implement get payment plans API endpoint 2024-02-21 18:15:32 +01:00
Faruk AYDIN
84a0b37fcc feat: Implement permission catalog API endpoint 2024-02-21 17:52:51 +01:00
Faruk AYDIN
f135a0f09e feat: Implement get role API endpoint for admin 2024-02-21 17:39:05 +01:00
Faruk AYDIN
0f24c99456 feat: Add permissions to role serializer 2024-02-21 16:01:45 +01:00
Faruk AYDIN
9eae0ab947 fix: Move get saml auth provider mocks to correct namespace 2024-02-21 15:37:39 +01:00
Faruk AYDIN
3bf1f79c79 feat: Implement admin get roles API endpoint 2024-02-21 15:35:30 +01:00
Faruk AYDIN
b21074c871 fix: Move saml auth provider router to correct folder 2024-02-21 14:59:42 +01:00
Ömer Faruk Aydın
d7893d9a32 Merge pull request #1621 from automatisch/show-saml-auth-provider
feat: Implement API endpoint to show saml auth provider
2024-02-20 13:00:25 +01:00
Ömer Faruk Aydın
9cbdda330c Merge pull request #1620 from automatisch/fix-authorization-middleware
fix: Include http methods for route rules
2024-02-20 12:59:12 +01:00
Ömer Faruk Aydın
42a9bfd099 Merge pull request #1619 from automatisch/get-saml-auth-providers
feat: Implement get saml auth providers API endpoint
2024-02-20 12:59:03 +01:00
Faruk AYDIN
eb15bd01ca feat: Implement API endpoint to show saml auth provider 2024-02-19 23:41:37 +01:00
Faruk AYDIN
9e98aebeb3 fix: Include http methods for route rules 2024-02-19 22:22:04 +01:00
Faruk AYDIN
1361cbc826 chore: Remove get saml auth providers from authorization list 2024-02-19 22:19:37 +01:00
Faruk AYDIN
679d0808a9 refactor: Move saml auth providers endpoint to admin namespace 2024-02-19 22:18:15 +01:00
Faruk AYDIN
6fe9a548ad feat: Implement get saml auth providers API endpoint 2024-02-19 21:48:06 +01:00
Faruk AYDIN
2d6d2430d2 fix: Detect types also for not paginated arrays 2024-02-19 21:46:20 +01:00
Faruk AYDIN
a445538e81 feat: Implement isCheckEnterprise middleware 2024-02-19 21:22:36 +01:00
Faruk AYDIN
50d38ffbd8 chore: Make http log level lower than info 2024-02-19 21:14:54 +01:00
Faruk AYDIN
93bcdfd9c9 feat: Implement saml auth provider serializer 2024-02-19 17:59:18 +01:00
Faruk AYDIN
5be3b101a5 feat: Implement saml auth provider factory 2024-02-19 17:58:52 +01:00
51 changed files with 1041 additions and 16 deletions

View File

@@ -0,0 +1,120 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Change a scheduled event',
key: 'changeScheduledEvent',
description: 'Changes a scheduled event',
arguments: [
{
label: 'Scheduled Event',
key: 'scheduledEventId',
type: 'dropdown',
required: true,
description: '',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listScheduledEvents',
},
],
},
},
{
label: 'Status',
key: 'status',
type: 'dropdown',
required: false,
description:
'After the status has been changed to COMPLETED or CANCELED, it becomes immutable and cannot be modified further.',
variables: true,
options: [
{ label: 'SCHEDULED', value: 1 },
{ label: 'ACTIVE', value: 2 },
{ label: 'COMPLETED', value: 3 },
{ label: 'CANCELED', value: 4 },
],
},
{
label: 'Type',
key: 'entityType',
type: 'dropdown',
required: true,
variables: true,
options: [
{ label: 'Stage channel', value: 1 },
{ label: 'Voice channel', value: 2 },
{ label: 'External', value: 3 },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listScheduledEventFieldsForChange',
},
{
name: 'parameters.entityType',
value: '{parameters.entityType}',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string',
required: false,
variables: true,
},
{
label: 'Description',
key: 'description',
type: 'string',
required: false,
variables: true,
},
{
label: 'Image',
key: 'image',
type: 'string',
required: false,
description:
'Image as DataURI scheme [data:image/<jpeg/png/gif>;base64,BASE64_ENCODED_<JPEG/PNG/GIF>_IMAGE_DATA]',
variables: true,
},
],
async run($) {
const data = {
channel_id: $.step.parameters.channel_id,
name: $.step.parameters.name,
scheduled_start_time: $.step.parameters.scheduledStartTime,
scheduled_end_time: $.step.parameters.scheduledEndTime,
description: $.step.parameters.description,
entity_type: $.step.parameters.entityType,
image: $.step.parameters.image,
};
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
data.entity_metadata = {
location: $.step.parameters.location,
};
data.channel_id = null;
}
const response = await $.http?.patch(
`/guilds/${$.auth.data.guildId}/scheduled-events/${$.step.parameters.scheduledEventId}`,
data
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,4 +1,9 @@
import changeScheduledEvent from './change-scheduled-event/index.js';
import sendMessageToChannel from './send-message-to-channel/index.js';
import createScheduledEvent from './create-scheduled-event/index.js';
export default [sendMessageToChannel, createScheduledEvent];
export default [
changeScheduledEvent,
sendMessageToChannel,
createScheduledEvent,
];

View File

@@ -1,4 +1,5 @@
import listChannels from './list-channels/index.js';
import listScheduledEvents from './list-scheduled-events/index.js';
import listVoiceChannels from './list-voice-channels/index.js';
export default [listChannels, listVoiceChannels];
export default [listChannels, listScheduledEvents, listVoiceChannels];

View File

@@ -0,0 +1,24 @@
export default {
name: 'List scheduled events',
key: 'listScheduledEvents',
async run($) {
const scheduledEvents = {
data: [],
error: null,
};
const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/scheduled-events`
);
scheduledEvents.data = response.data.map((scheduledEvent) => {
return {
value: scheduledEvent.id,
name: scheduledEvent.name,
};
});
return scheduledEvents;
},
};

View File

@@ -1,3 +1,7 @@
import listExternalScheduledEventFields from './list-external-scheduled-event-fields/index.js';
import listScheduledEventFieldsForChange from './list-scheduled-event-fields-for-change/index.js';
export default [listExternalScheduledEventFields];
export default [
listExternalScheduledEventFields,
listScheduledEventFieldsForChange,
];

View File

@@ -0,0 +1,87 @@
export default {
name: 'List scheduled event fields for change',
key: 'listScheduledEventFieldsForChange',
async run($) {
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
return [
{
label: 'Location',
key: 'location',
type: 'string',
required: true,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: true,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
}
return [
{
label: 'Channel',
key: 'channel_id',
type: 'dropdown',
required: true,
description:
'Pick a voice or stage channel to link the event to. This will be omitted if type is EXTERNAL',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceChannels',
},
],
},
},
{
label: 'Location',
key: 'location',
type: 'string',
required: false,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: false,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
},
};

View File

@@ -0,0 +1,6 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import permissionCatalog from '../../../../../helpers/permission-catalog.ee.js';
export default async (request, response) => {
renderObject(response, permissionCatalog);
};

View File

@@ -0,0 +1,32 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getPermissionsCatalogMock from '../../../../../../test/mocks/rest/api/v1/admin/permissions/get-permissions-catalog.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/permissions/catalog', () => {
let role, currentUser, token;
beforeEach(async () => {
role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/permissions/catalog')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPermissionsCatalogMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,16 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const role = await Role.query()
.leftJoinRelated({
permissions: true,
})
.withGraphFetched({
permissions: true,
})
.findById(request.params.roleId)
.throwIfNotFound();
renderObject(response, role);
};

View File

@@ -0,0 +1,38 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createPermission } from '../../../../../../test/factories/permission.js';
import getRoleMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-role.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/roles/:roleId', () => {
let role, currentUser, token, permissionOne, permissionTwo;
beforeEach(async () => {
role = await createRole({ key: 'admin' });
permissionOne = await createPermission({ roleId: role.id });
permissionTwo = await createPermission({ roleId: role.id });
currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getRoleMock(role, [
permissionOne,
permissionTwo,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const roles = await Role.query().orderBy('name');
renderObject(response, roles);
};

View File

@@ -0,0 +1,33 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getRolesMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-roles.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/roles', () => {
let roleOne, roleTwo, currentUser, token;
beforeEach(async () => {
roleOne = await createRole({ key: 'admin' });
roleTwo = await createRole({ key: 'user' });
currentUser = await createUser({ roleId: roleOne.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/roles')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getRolesMock([roleOne, roleTwo]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,10 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProvider = await SamlAuthProvider.query()
.findById(request.params.samlAuthProviderId)
.throwIfNotFound();
renderObject(response, samlAuthProvider);
};

View File

@@ -0,0 +1,34 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import getSamlAuthProviderMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
let samlAuthProvider, currentUser, token;
beforeEach(async () => {
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return saml auth provider with specified id', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getSamlAuthProviderMock(samlAuthProvider);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,11 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProviders = await SamlAuthProvider.query().orderBy(
'created_at',
'desc'
);
renderObject(response, samlAuthProviders);
};

View File

@@ -0,0 +1,39 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import getSamlAuthProvidersMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-providers', () => {
let samlAuthProviderOne, samlAuthProviderTwo, currentUser, token;
beforeEach(async () => {
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProviderOne = await createSamlAuthProvider();
samlAuthProviderTwo = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return saml auth providers', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/saml-auth-providers')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getSamlAuthProvidersMock([
samlAuthProviderTwo,
samlAuthProviderOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,19 @@
import { renderObject } from '../../../../helpers/renderer.js';
import axios from '../../../../helpers/axios-with-proxy.js';
import logger from '../../../../helpers/logger.js';
const NOTIFICATIONS_URL =
'https://notifications.automatisch.io/notifications.json';
export default async (request, response) => {
let notifications = [];
try {
const response = await axios.get(NOTIFICATIONS_URL);
notifications = response.data;
} catch (error) {
logger.error('Error fetching notifications API endpoint!', error);
}
renderObject(response, notifications);
};

View File

@@ -0,0 +1,9 @@
import { describe, it } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
describe('GET /api/v1/automatisch/notifications', () => {
it('should return Automatisch notifications', async () => {
await request(app).get('/api/v1/automatisch/notifications').expect(200);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../helpers/renderer.js';
import Billing from '../../../../helpers/billing/index.ee.js';
export default async (request, response) => {
const paddleInfo = Billing.paddleInfo;
renderObject(response, paddleInfo);
};

View File

@@ -0,0 +1,33 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getPaddleInfoMock from '../../../../../test/mocks/rest/api/v1/payment/get-paddle-info.js';
import appConfig from '../../../../config/app.js';
import billing from '../../../../helpers/billing/index.ee.js';
describe('GET /api/v1/payment/paddle-info', () => {
let user, token;
beforeEach(async () => {
user = await createUser();
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
vi.spyOn(billing.paddleInfo, 'vendorId', 'get').mockReturnValue(
'sampleVendorId'
);
});
it('should return payment plans', async () => {
const response = await request(app)
.get('/api/v1/payment/paddle-info')
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getPaddleInfoMock();
expect(response.body).toEqual(expectedResponsePayload);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../helpers/renderer.js';
import Billing from '../../../../helpers/billing/index.ee.js';
export default async (request, response) => {
const paymentPlans = Billing.paddlePlans;
renderObject(response, paymentPlans);
};

View File

@@ -0,0 +1,29 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getPaymentPlansMock from '../../../../../test/mocks/rest/api/v1/payment/get-plans.js';
import appConfig from '../../../../config/app.js';
describe('GET /api/v1/payment/plans', () => {
let user, token;
beforeEach(async () => {
user = await createUser();
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return payment plans', async () => {
const response = await request(app)
.get('/api/v1/payment/plans')
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getPaymentPlansMock();
expect(response.body).toEqual(expectedResponsePayload);
});
});

View File

@@ -1,16 +1,17 @@
const authorizationList = {
'/api/v1/users/:userId': {
'GET /api/v1/users/:userId': {
action: 'read',
subject: 'User',
},
'/api/v1/users/': {
'GET /api/v1/users/': {
action: 'read',
subject: 'User',
},
};
export const authorizeUser = async (request, response, next) => {
const currentRoute = request.baseUrl + request.route.path;
const currentRoute =
request.method + ' ' + request.baseUrl + request.route.path;
const currentRouteRule = authorizationList[currentRoute];
try {
@@ -20,3 +21,13 @@ export const authorizeUser = async (request, response, next) => {
return response.status(403).end();
}
};
export const authorizeAdmin = async (request, response, next) => {
const role = await request.currentUser.$relatedQuery('role');
if (role?.isAdmin) {
next();
} else {
return response.status(403).end();
}
};

View File

@@ -0,0 +1,9 @@
import { hasValidLicense } from './license.ee.js';
export const checkIsEnterprise = async (request, response, next) => {
if (await hasValidLicense()) {
next();
} else {
return response.status(404).end();
}
};

View File

@@ -4,8 +4,8 @@ import appConfig from '../config/app.js';
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
http: 2,
info: 3,
debug: 4,
};

View File

@@ -15,6 +15,8 @@ const renderObject = (response, object) => {
let data = isPaginated(object) ? object.records : object;
const type = isPaginated(object)
? object.records[0].constructor.name
: Array.isArray(object)
? object[0].constructor.name
: object.constructor.name;
const serializer = serializers[type];

View File

@@ -0,0 +1,17 @@
import { Router } from 'express';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getPermissionsCatalogAction from '../../../../controllers/api/v1/admin/permissions/get-permissions-catalog.ee.js';
const router = Router();
router.get(
'/catalog',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getPermissionsCatalogAction
);
export default router;

View File

@@ -0,0 +1,26 @@
import { Router } from 'express';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getRolesAction from '../../../../controllers/api/v1/admin/roles/get-roles.ee.js';
import getRoleAction from '../../../../controllers/api/v1/admin/roles/get-role.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getRolesAction
);
router.get(
'/:roleId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getRoleAction
);
export default router;

View File

@@ -0,0 +1,26 @@
import { Router } from 'express';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getSamlAuthProvidersAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import getSamlAuthProviderAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getSamlAuthProvidersAction
);
router.get(
'/:samlAuthProviderId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getSamlAuthProviderAction
);
export default router;

View File

@@ -1,8 +1,10 @@
import { Router } from 'express';
import versionAction from '../../../controllers/api/v1/automatisch/version.js';
import notificationsAction from '../../../controllers/api/v1/automatisch/notifications.js';
const router = Router();
router.get('/version', versionAction);
router.get('/notifications', notificationsAction);
export default router;

View File

@@ -0,0 +1,12 @@
import { Router } from 'express';
import { authenticateUser } from '../../../helpers/authentication.js';
import checkIsCloud from '../../../helpers/check-is-cloud.js';
import getPlansAction from '../../../controllers/api/v1/payment/get-plans.ee.js';
import getPaddleInfoAction from '../../../controllers/api/v1/payment/get-paddle-info.ee.js';
const router = Router();
router.get('/plans', authenticateUser, checkIsCloud, getPlansAction);
router.get('/paddle-info', authenticateUser, checkIsCloud, getPaddleInfoAction);
export default router;

View File

@@ -5,6 +5,10 @@ import paddleRouter from './paddle.ee.js';
import healthcheckRouter from './healthcheck.js';
import automatischRouter from './api/v1/automatisch.js';
import usersRouter from './api/v1/users.js';
import paymentRouter from './api/v1/payment.ee.js';
import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
import rolesRouter from './api/v1/admin/roles.ee.js';
import permissionsRouter from './api/v1/admin/permissions.ee.js';
const router = Router();
@@ -14,5 +18,9 @@ router.use('/paddle', paddleRouter);
router.use('/healthcheck', healthcheckRouter);
router.use('/api/v1/automatisch', automatischRouter);
router.use('/api/v1/users', usersRouter);
router.use('/api/v1/payment', paymentRouter);
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
router.use('/api/v1/admin/roles', rolesRouter);
router.use('/api/v1/admin/permissions', permissionsRouter);
export default router;

View File

@@ -1,11 +1,13 @@
import userSerializer from './user.js';
import roleSerializer from './role.js';
import permissionSerializer from './permission.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
const serializers = {
User: userSerializer,
Role: roleSerializer,
Permission: permissionSerializer,
SamlAuthProvider: samlAuthProviderSerializer,
};
export default serializers;

View File

@@ -1,5 +1,7 @@
import permissionSerializer from './permission.js';
const roleSerializer = (role) => {
return {
let roleData = {
id: role.id,
name: role.name,
key: role.key,
@@ -8,6 +10,14 @@ const roleSerializer = (role) => {
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};
if (role.permissions) {
roleData.permissions = role.permissions.map((permission) =>
permissionSerializer(permission)
);
}
return roleData;
};
export default roleSerializer;

View File

@@ -1,12 +1,25 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createRole } from '../../test/factories/role';
import roleSerializer from './role';
import { createPermission } from '../../test/factories/permission';
describe('roleSerializer', () => {
let role;
let role, permissionOne, permissionTwo;
beforeEach(async () => {
role = await createRole();
permissionOne = await createPermission({
roleId: role.id,
action: 'read',
subject: 'User',
});
permissionTwo = await createPermission({
roleId: role.id,
action: 'read',
subject: 'Role',
});
});
it('should return role data', async () => {
@@ -22,4 +35,14 @@ describe('roleSerializer', () => {
expect(roleSerializer(role)).toEqual(expectedPayload);
});
it('should return role data with the permissions', async () => {
role.permissions = [permissionOne, permissionTwo];
const expectedPayload = {
permissions: [permissionOne, permissionTwo],
};
expect(roleSerializer(role)).toMatchObject(expectedPayload);
});
});

View File

@@ -0,0 +1,18 @@
const samlAuthProviderSerializer = (samlAuthProvider) => {
return {
id: samlAuthProvider.id,
name: samlAuthProvider.name,
certificate: samlAuthProvider.certificate,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
issuer: samlAuthProvider.issuer,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
emailAttributeName: samlAuthProvider.emailAttributeName,
roleAttributeName: samlAuthProvider.roleAttributeName,
active: samlAuthProvider.active,
defaultRoleId: samlAuthProvider.defaultRoleId,
};
};
export default samlAuthProviderSerializer;

View File

@@ -0,0 +1,32 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createSamlAuthProvider } from '../../test/factories/saml-auth-provider.ee.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
describe('samlAuthProviderSerializer', () => {
let samlAuthProvider;
beforeEach(async () => {
samlAuthProvider = await createSamlAuthProvider();
});
it('should return saml auth provider data', async () => {
const expectedPayload = {
id: samlAuthProvider.id,
name: samlAuthProvider.name,
certificate: samlAuthProvider.certificate,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
issuer: samlAuthProvider.issuer,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
emailAttributeName: samlAuthProvider.emailAttributeName,
roleAttributeName: samlAuthProvider.roleAttributeName,
active: samlAuthProvider.active,
defaultRoleId: samlAuthProvider.defaultRoleId,
};
expect(samlAuthProviderSerializer(samlAuthProvider)).toEqual(
expectedPayload
);
});
});

View File

@@ -9,7 +9,6 @@ const userSerializer = (user) => {
createdAt: user.createdAt,
updatedAt: user.updatedAt,
fullName: user.fullName,
roleId: user.roleId,
};
if (user.role) {

View File

@@ -33,7 +33,6 @@ describe('userSerializer', () => {
email: user.email,
fullName: user.fullName,
id: user.id,
roleId: user.roleId,
updatedAt: user.updatedAt,
};

View File

@@ -0,0 +1,33 @@
import { createRole } from './role';
import SamlAuthProvider from '../../src/models/saml-auth-provider.ee.js';
export const createSamlAuthProvider = async (params = {}) => {
params.name = params?.name || 'Keycloak SAML';
params.certificate = params?.certificate || 'certificate';
params.signatureAlgorithm = params?.signatureAlgorithm || 'sha512';
params.entryPoint =
params?.entryPoint ||
'https://example.com/auth/realms/automatisch/protocol/saml';
params.issuer = params?.issuer || 'automatisch-client';
params.firstnameAttributeName =
params?.firstnameAttributeName || 'urn:oid:2.1.1.42';
params.surnameAttributeName =
params?.surnameAttributeName || 'urn:oid:2.1.1.4';
params.emailAttributeName =
params?.emailAttributeName || 'urn:oid:1.1.2342.19200300.100.1.1';
params.roleAttributeName = params?.roleAttributeName || 'Role';
params.defaultRoleId = params?.defaultRoleId || (await createRole()).id;
params.active = params?.active || true;
const samlAuthProvider = await SamlAuthProvider.query()
.insert(params)
.returning('*');
return samlAuthProvider;
};

View File

@@ -0,0 +1,64 @@
const getPermissionsCatalogMock = async () => {
const data = {
actions: [
{
key: 'create',
label: 'Create',
subjects: ['Connection', 'Flow'],
},
{
key: 'read',
label: 'Read',
subjects: ['Connection', 'Execution', 'Flow'],
},
{
key: 'update',
label: 'Update',
subjects: ['Connection', 'Flow'],
},
{
key: 'delete',
label: 'Delete',
subjects: ['Connection', 'Flow'],
},
{
key: 'publish',
label: 'Publish',
subjects: ['Flow'],
},
],
conditions: [
{
key: 'isCreator',
label: 'Is creator',
},
],
subjects: [
{
key: 'Connection',
label: 'Connection',
},
{
key: 'Flow',
label: 'Flow',
},
{
key: 'Execution',
label: 'Execution',
},
],
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getPermissionsCatalogMock;

View File

@@ -0,0 +1,33 @@
const getRoleMock = async (role, permissions) => {
const data = {
id: role.id,
key: role.key,
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
permissions: permissions.map((permission) => ({
id: permission.id,
action: permission.action,
conditions: permission.conditions,
roleId: permission.roleId,
subject: permission.subject,
createdAt: permission.createdAt.toISOString(),
updatedAt: permission.updatedAt.toISOString(),
})),
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Role',
},
};
};
export default getRoleMock;

View File

@@ -0,0 +1,26 @@
const getRolesMock = async (roles) => {
const data = roles.map((role) => {
return {
id: role.id,
key: role.key,
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Role',
},
};
};
export default getRolesMock;

View File

@@ -0,0 +1,29 @@
const getSamlAuthProviderMock = async (samlAuthProvider) => {
const data = {
active: samlAuthProvider.active,
certificate: samlAuthProvider.certificate,
defaultRoleId: samlAuthProvider.defaultRoleId,
emailAttributeName: samlAuthProvider.emailAttributeName,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
id: samlAuthProvider.id,
issuer: samlAuthProvider.issuer,
name: samlAuthProvider.name,
roleAttributeName: samlAuthProvider.roleAttributeName,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'SamlAuthProvider',
},
};
};
export default getSamlAuthProviderMock;

View File

@@ -0,0 +1,31 @@
const getSamlAuthProvidersMock = async (samlAuthProviders) => {
const data = samlAuthProviders.map((samlAuthProvider) => {
return {
active: samlAuthProvider.active,
certificate: samlAuthProvider.certificate,
defaultRoleId: samlAuthProvider.defaultRoleId,
emailAttributeName: samlAuthProvider.emailAttributeName,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
id: samlAuthProvider.id,
issuer: samlAuthProvider.issuer,
name: samlAuthProvider.name,
roleAttributeName: samlAuthProvider.roleAttributeName,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'SamlAuthProvider',
},
};
};
export default getSamlAuthProvidersMock;

View File

@@ -0,0 +1,17 @@
const getPaddleInfoMock = async () => {
return {
data: {
sandbox: true,
vendorId: 'sampleVendorId',
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getPaddleInfoMock;

View File

@@ -0,0 +1,22 @@
const getPaymentPlansMock = async () => {
return {
data: [
{
limit: '10,000',
name: '10k - monthly',
price: '€20',
productId: '47384',
quota: 10000,
},
],
meta: {
count: 1,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Object',
},
};
};
export default getPaymentPlansMock;

View File

@@ -15,7 +15,6 @@ const getCurrentUserMock = (currentUser, role) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},

View File

@@ -14,7 +14,6 @@ const getUserMock = (currentUser, role) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},

View File

@@ -1,6 +1,7 @@
const getUsersMock = async (users, roles) => {
const data = users.map((user) => {
const role = roles.find((r) => r.id === user.roleId);
return {
createdAt: user.createdAt.toISOString(),
email: user.email,
@@ -16,8 +17,7 @@ const getUsersMock = async (users, roles) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
}
: null, // Fallback to null if role not found
roleId: user.roleId,
: null,
trialExpiryDate: user.trialExpiryDate.toISOString(),
updatedAt: user.updatedAt.toISOString(),
};

View File

@@ -1,6 +1,8 @@
---
favicon: /favicons/discord.svg
items:
- name: Change a scheduled event
desc: Changes a scheduled event.
- name: Send a message to channel
desc: Sends a message to a specific channel you specify.
- name: Create a scheduled event