Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
77f84944c7 | ||
![]() |
8a8be21d56 | ||
![]() |
e5c4e18fd5 | ||
![]() |
953c5a5b5b | ||
![]() |
4313265c00 | ||
![]() |
9405f267ba | ||
![]() |
1d29238199 | ||
![]() |
c5bf66f462 | ||
![]() |
e6180bdfaa | ||
![]() |
55c391afc8 | ||
![]() |
782fa67320 |
@@ -6,8 +6,7 @@
|
|||||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||||
"start:web": "lerna run --stream --scope=@*/web dev",
|
"start:web": "lerna run --stream --scope=@*/web dev",
|
||||||
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
||||||
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} lint",
|
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
|
||||||
"build:watch": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} build:watch",
|
|
||||||
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Acknowledge incident',
|
||||||
|
key: 'acknowledgeIncident',
|
||||||
|
description: 'Acknowledges an incident.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Incident ID',
|
||||||
|
key: 'incidentId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'This serves as the incident ID that requires your acknowledgment.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Acknowledged by',
|
||||||
|
key: 'acknowledgedBy',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
"This refers to the individual's name, email, or another form of identification that the person who acknowledged the incident has provided.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const acknowledgedBy = $.step.parameters.acknowledgedBy;
|
||||||
|
const incidentId = $.step.parameters.incidentId;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
acknowledged_by: acknowledgedBy,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post(
|
||||||
|
`/v2/incidents/${incidentId}/acknowledge`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
$.setActionItem({ raw: response.data.data });
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/better-stack/actions/index.js
Normal file
3
packages/backend/src/apps/better-stack/actions/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import acknowledgeIncident from './acknowledge-incident/index.js';
|
||||||
|
|
||||||
|
export default [acknowledgeIncident];
|
21
packages/backend/src/apps/better-stack/assets/favicon.svg
Normal file
21
packages/backend/src/apps/better-stack/assets/favicon.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000" stroke="none">
|
||||||
|
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
||||||
|
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
||||||
|
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
||||||
|
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
||||||
|
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
||||||
|
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
||||||
|
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
||||||
|
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
||||||
|
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
||||||
|
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
33
packages/backend/src/apps/better-stack/auth/index.js
Normal file
33
packages/backend/src/apps/better-stack/auth/index.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'Screen name of your connection to be used on Automatisch UI.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'Better Stack API key of your account.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,10 @@
|
|||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
await $.http.get('/v2/metadata');
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: $.auth.data.screenName,
|
||||||
|
apiKey: $.auth.data.apiKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.apiKey) {
|
||||||
|
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
18
packages/backend/src/apps/better-stack/index.js
Normal file
18
packages/backend/src/apps/better-stack/index.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Better Stack',
|
||||||
|
key: 'better-stack',
|
||||||
|
iconUrl: '{BASE_URL}/apps/better-stack/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/better-stack/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://betterstack.com',
|
||||||
|
apiBaseUrl: 'https://uptime.betterstack.com/api',
|
||||||
|
primaryColor: '000000',
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
actions,
|
||||||
|
});
|
@@ -6,31 +6,6 @@ import { createRole } from '../../../test/factories/role';
|
|||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
|
||||||
describe('graphQL getCurrentUser query', () => {
|
describe('graphQL getCurrentUser query', () => {
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const invalidUserToken = 'invalid-token';
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getCurrentUser {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidUserToken)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
let role, currentUser, token, requestObject;
|
let role, currentUser, token, requestObject;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -101,5 +76,4 @@ describe('graphQL getCurrentUser query', () => {
|
|||||||
'Cannot query field "password" on type "User".'
|
'Cannot query field "password" on type "User".'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -40,23 +40,7 @@ describe('graphQL getExecutions query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const invalidToken = 'invalid-token';
|
describe('and without correct permissions', () => {
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and without permissions', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const userWithoutPermissions = await createUser();
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
||||||
@@ -485,5 +469,4 @@ describe('graphQL getExecutions query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -40,23 +40,6 @@ describe('graphQL getFlow query', () => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const invalidToken = 'invalid-token';
|
|
||||||
const flow = await createFlow();
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query: query(flow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and without permissions', () => {
|
describe('and without permissions', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const userWithoutPermissions = await createUser();
|
||||||
@@ -145,9 +128,7 @@ describe('graphQL getFlow query', () => {
|
|||||||
{
|
{
|
||||||
appKey: actionStep.appKey,
|
appKey: actionStep.appKey,
|
||||||
connection: {
|
connection: {
|
||||||
createdAt: actionConnection.createdAt
|
createdAt: actionConnection.createdAt.getTime().toString(),
|
||||||
.getTime()
|
|
||||||
.toString(),
|
|
||||||
id: actionConnection.id,
|
id: actionConnection.id,
|
||||||
verified: actionConnection.verified,
|
verified: actionConnection.verified,
|
||||||
},
|
},
|
||||||
@@ -234,9 +215,7 @@ describe('graphQL getFlow query', () => {
|
|||||||
{
|
{
|
||||||
appKey: actionStep.appKey,
|
appKey: actionStep.appKey,
|
||||||
connection: {
|
connection: {
|
||||||
createdAt: actionConnection.createdAt
|
createdAt: actionConnection.createdAt.getTime().toString(),
|
||||||
.getTime()
|
|
||||||
.toString(),
|
|
||||||
id: actionConnection.id,
|
id: actionConnection.id,
|
||||||
verified: actionConnection.verified,
|
verified: actionConnection.verified,
|
||||||
},
|
},
|
||||||
@@ -258,5 +237,4 @@ describe('graphQL getFlow query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -17,7 +17,6 @@ describe('graphQL getRole query', () => {
|
|||||||
userWithoutPermissions,
|
userWithoutPermissions,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions,
|
||||||
tokenWithoutPermissions,
|
tokenWithoutPermissions,
|
||||||
invalidToken,
|
|
||||||
permissionOne,
|
permissionOne,
|
||||||
permissionTwo;
|
permissionTwo;
|
||||||
|
|
||||||
@@ -74,24 +73,8 @@ describe('graphQL getRole query', () => {
|
|||||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||||
userWithoutPermissions.id
|
userWithoutPermissions.id
|
||||||
);
|
);
|
||||||
|
|
||||||
invalidToken = 'invalid-token';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query: queryWithValidRole })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and with valid license', () => {
|
describe('and with valid license', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
@@ -178,5 +161,4 @@ describe('graphQL getRole query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -15,8 +15,7 @@ describe('graphQL getRoles query', () => {
|
|||||||
userWithPermissions,
|
userWithPermissions,
|
||||||
userWithoutPermissions,
|
userWithoutPermissions,
|
||||||
tokenWithPermissions,
|
tokenWithPermissions,
|
||||||
tokenWithoutPermissions,
|
tokenWithoutPermissions;
|
||||||
invalidToken;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUserRole = await createRole({ name: 'Current user role' });
|
currentUserRole = await createRole({ name: 'Current user role' });
|
||||||
@@ -53,24 +52,8 @@ describe('graphQL getRoles query', () => {
|
|||||||
tokenWithoutPermissions = createAuthTokenByUserId(
|
tokenWithoutPermissions = createAuthTokenByUserId(
|
||||||
userWithoutPermissions.id
|
userWithoutPermissions.id
|
||||||
);
|
);
|
||||||
|
|
||||||
invalidToken = 'invalid-token';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and with valid license', () => {
|
describe('and with valid license', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
@@ -148,5 +131,4 @@ describe('graphQL getRoles query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -16,22 +16,6 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const invalidToken = 'invalid-token';
|
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', invalidToken)
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
let user, userToken;
|
let user, userToken;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -113,5 +97,4 @@ describe('graphQL getTrialStatus query', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -8,31 +8,6 @@ import { createPermission } from '../../../test/factories/permission';
|
|||||||
import { createUser } from '../../../test/factories/user';
|
import { createUser } from '../../../test/factories/user';
|
||||||
|
|
||||||
describe('graphQL getUser query', () => {
|
describe('graphQL getUser query', () => {
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const invalidUserId = '123123123';
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
getUser(id: "${invalidUserId}") {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', 'invalid-token')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and without permissions', () => {
|
describe('and without permissions', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const userWithoutPermissions = await createUser();
|
||||||
@@ -84,9 +59,7 @@ describe('graphQL getUser query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
requestObject = request(app)
|
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return user data for a valid user id', async () => {
|
it('should return user data for a valid user id', async () => {
|
||||||
@@ -170,5 +143,4 @@ describe('graphQL getUser query', () => {
|
|||||||
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
expect(response.body.errors[0].message).toEqual('NotFoundError');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -30,20 +30,6 @@ describe('graphQL getUsers query', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
describe('with unauthenticated user', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', 'invalid-token')
|
|
||||||
.send({ query })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not Authorised!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with authenticated user', () => {
|
|
||||||
describe('and without permissions', () => {
|
describe('and without permissions', () => {
|
||||||
it('should throw not authorized error', async () => {
|
it('should throw not authorized error', async () => {
|
||||||
const userWithoutPermissions = await createUser();
|
const userWithoutPermissions = await createUser();
|
||||||
@@ -86,9 +72,7 @@ describe('graphQL getUsers query', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
requestObject = request(app)
|
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return users data', async () => {
|
it('should return users data', async () => {
|
||||||
@@ -161,5 +145,4 @@ describe('graphQL getUsers query', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
|
|||||||
import appConfig from '../config/app.js';
|
import appConfig from '../config/app.js';
|
||||||
import User from '../models/user.js';
|
import User from '../models/user.js';
|
||||||
|
|
||||||
const isAuthenticated = rule()(async (_parent, _args, req) => {
|
export const isAuthenticated = async (_parent, _args, req) => {
|
||||||
const token = req.headers['authorization'];
|
const token = req.headers['authorization'];
|
||||||
|
|
||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
@@ -26,12 +26,13 @@ const isAuthenticated = rule()(async (_parent, _args, req) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
const authentication = shield(
|
const isAuthenticatedRule = rule()(isAuthenticated);
|
||||||
{
|
|
||||||
|
export const authenticationRules = {
|
||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticatedRule,
|
||||||
getAutomatischInfo: allow,
|
getAutomatischInfo: allow,
|
||||||
getConfig: allow,
|
getConfig: allow,
|
||||||
getNotifications: allow,
|
getNotifications: allow,
|
||||||
@@ -39,16 +40,18 @@ const authentication = shield(
|
|||||||
listSamlAuthProviders: allow,
|
listSamlAuthProviders: allow,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticatedRule,
|
||||||
forgotPassword: allow,
|
forgotPassword: allow,
|
||||||
login: allow,
|
login: allow,
|
||||||
registerUser: allow,
|
registerUser: allow,
|
||||||
resetPassword: allow,
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
{
|
|
||||||
|
const authenticationOptions = {
|
||||||
allowExternalErrors: true,
|
allowExternalErrors: true,
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
const authentication = shield(authenticationRules, authenticationOptions);
|
||||||
|
|
||||||
export default authentication;
|
export default authentication;
|
||||||
|
78
packages/backend/src/helpers/authentication.test.js
Normal file
78
packages/backend/src/helpers/authentication.test.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { allow } from 'graphql-shield';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import User from '../models/user.js';
|
||||||
|
import { isAuthenticated, authenticationRules } from './authentication.js';
|
||||||
|
|
||||||
|
vi.mock('jsonwebtoken');
|
||||||
|
vi.mock('../models/user.js');
|
||||||
|
|
||||||
|
describe('isAuthenticated', () => {
|
||||||
|
it('should return false if no token is provided', async () => {
|
||||||
|
const req = { headers: {} };
|
||||||
|
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if token is invalid', async () => {
|
||||||
|
jwt.verify.mockImplementation(() => {
|
||||||
|
throw new Error('invalid token');
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { headers: { authorization: 'invalidToken' } };
|
||||||
|
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if token is valid', async () => {
|
||||||
|
jwt.verify.mockReturnValue({ userId: '123' });
|
||||||
|
|
||||||
|
User.query.mockReturnValue({
|
||||||
|
findById: vi.fn().mockReturnValue({
|
||||||
|
leftJoinRelated: vi.fn().mockReturnThis(),
|
||||||
|
withGraphFetched: vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ id: '123', role: {}, permissions: {} }),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { headers: { authorization: 'validToken' } };
|
||||||
|
expect(await isAuthenticated(null, null, req)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authentication rules', () => {
|
||||||
|
const getQueryAndMutationNames = (rules) => {
|
||||||
|
const queries = Object.keys(rules.Query || {});
|
||||||
|
const mutations = Object.keys(rules.Mutation || {});
|
||||||
|
return { queries, mutations };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { queries, mutations } = getQueryAndMutationNames(authenticationRules);
|
||||||
|
|
||||||
|
describe('for queries', () => {
|
||||||
|
queries.forEach((query) => {
|
||||||
|
it(`should apply correct rule for query: ${query}`, () => {
|
||||||
|
const ruleApplied = authenticationRules.Query[query];
|
||||||
|
|
||||||
|
if (query === '*') {
|
||||||
|
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||||
|
} else {
|
||||||
|
expect(ruleApplied).toEqual(allow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for mutations', () => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
it(`should apply correct rule for mutation: ${mutation}`, () => {
|
||||||
|
const ruleApplied = authenticationRules.Mutation[mutation];
|
||||||
|
|
||||||
|
if (mutation === '*') {
|
||||||
|
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||||
|
} else {
|
||||||
|
expect(ruleApplied).toBe(allow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -32,6 +32,15 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
'/apps/': [
|
'/apps/': [
|
||||||
|
{
|
||||||
|
text: 'Better Stack',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Actions', link: '/apps/better-stack/actions' },
|
||||||
|
{ text: 'Connection', link: '/apps/better-stack/connection' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Carbone',
|
text: 'Carbone',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -305,7 +314,7 @@ export default defineConfig({
|
|||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Actions', link: '/apps/removebg/actions' },
|
{ text: 'Actions', link: '/apps/removebg/actions' },
|
||||||
{ text: 'Connection', link: '/apps/removebg/connection' }
|
{ text: 'Connection', link: '/apps/removebg/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
12
packages/docs/pages/apps/better-stack/actions.md
Normal file
12
packages/docs/pages/apps/better-stack/actions.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/better-stack.svg
|
||||||
|
items:
|
||||||
|
- name: Acknowledge incident
|
||||||
|
desc: Acknowledges an incident.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
14
packages/docs/pages/apps/better-stack/connection.md
Normal file
14
packages/docs/pages/apps/better-stack/connection.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Better Stack
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Better Stack
|
||||||
|
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Login to your Better Stack account: [https://betterstack.com/](https://betterstack.com/).
|
||||||
|
2. Click on the team name bottom left and select **Manage Teams** option.
|
||||||
|
3. Click on the three dots icon of your team and select **manage** option.
|
||||||
|
4. Click on the **API tokens** tab.
|
||||||
|
5. Copy the token next to **Direct API tokens** to the `API Key` field on Automatisch.
|
||||||
|
6. Fill the screen name on Automatisch.
|
||||||
|
7. Now, you can start using the Better Stack connection with Automatisch.
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
The following integrations are currently supported by Automatisch.
|
The following integrations are currently supported by Automatisch.
|
||||||
|
|
||||||
|
- [Better Stack](/apps/better-stack/actions)
|
||||||
- [Carbone](/apps/carbone/actions)
|
- [Carbone](/apps/carbone/actions)
|
||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
- [Delay](/apps/delay/actions)
|
- [Delay](/apps/delay/actions)
|
||||||
|
21
packages/docs/pages/public/favicons/better-stack.svg
Normal file
21
packages/docs/pages/public/favicons/better-stack.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
|
||||||
|
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
|
||||||
|
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
|
||||||
|
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
|
||||||
|
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
|
||||||
|
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
|
||||||
|
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
|
||||||
|
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
|
||||||
|
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
|
||||||
|
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user