Compare commits
1 Commits
test-docs-
...
AUT-405
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a650e3beaa |
34
packages/backend/src/apps/amazon-s3/assets/favicon.svg
Normal file
34
packages/backend/src/apps/amazon-s3/assets/favicon.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="428" height="512" viewBox="0 0 428 512">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #e25444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1, .cls-2, .cls-3 {
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #7b1d13;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #58150d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-1" d="M378,99L295,257l83,158,34-19V118Z"/>
|
||||||
|
<path class="cls-2" d="M378,99L212,118,127.5,257,212,396l166,19V99Z"/>
|
||||||
|
<path class="cls-3" d="M43,99L16,111V403l27,12L212,257Z"/>
|
||||||
|
<path class="cls-1" d="M42.637,98.667l169.587,47.111V372.444L42.637,415.111V98.667Z"/>
|
||||||
|
<path class="cls-3" d="M212.313,170.667l-72.008-11.556,72.008-81.778,71.83,81.778Z"/>
|
||||||
|
<path class="cls-3" d="M284.143,159.111l-71.919,11.733-71.919-11.733V77.333"/>
|
||||||
|
<path class="cls-3" d="M212.313,342.222l-72.008,13.334,72.008,70.222,71.83-70.222Z"/>
|
||||||
|
<path class="cls-2" d="M212,16L140,54V159l72.224-20.333Z"/>
|
||||||
|
<path class="cls-2" d="M212.224,196.444l-71.919,7.823V309.105l71.919,8.228V196.444Z"/>
|
||||||
|
<path class="cls-2" d="M212.224,373.333L140.305,355.3V458.363L212.224,496V373.333Z"/>
|
||||||
|
<path class="cls-1" d="M284.143,355.3l-71.919,18.038V496l71.919-37.637V355.3Z"/>
|
||||||
|
<path class="cls-1" d="M212.224,196.444l71.919,7.823V309.105l-71.919,8.228V196.444Z"/>
|
||||||
|
<path class="cls-1" d="M212,16l72,38V159l-72-20V16Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
56
packages/backend/src/apps/amazon-s3/auth/index.ts
Normal file
56
packages/backend/src/apps/amazon-s3/auth/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/amazon-s3/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in AWS, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'accessKeyId',
|
||||||
|
label: 'Access Key ID',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'secretAccessKey',
|
||||||
|
label: 'Secret Access Key',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'Screen name of your connection to be used on Automatisch UI.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.resourceName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const { data } = await $.http.get('/');
|
||||||
|
|
||||||
|
console.log('data:', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
153
packages/backend/src/apps/amazon-s3/common/add-auth-header.ts
Normal file
153
packages/backend/src/apps/amazon-s3/common/add-auth-header.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { getISODate, getYYYYMMDD } from './get-current-date';
|
||||||
|
|
||||||
|
function hmac(key: string | Buffer, data: string) {
|
||||||
|
return crypto.createHmac('sha256', key).update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmacWoHex(key: Buffer | string, data: string) {
|
||||||
|
return crypto.createHmac('sha256', key).update(data).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(data: string) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareCanonicalRequest(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
queryParams: IJSONObject | string,
|
||||||
|
headers: IJSONObject,
|
||||||
|
payload: string
|
||||||
|
) {
|
||||||
|
const canonicalRequest = [method, encodeURIComponent(path)];
|
||||||
|
|
||||||
|
// Step 3: Canonical Query String
|
||||||
|
if (typeof queryParams === 'string') {
|
||||||
|
canonicalRequest.push('');
|
||||||
|
} else {
|
||||||
|
const sortedQueryParams = Object.keys(queryParams)
|
||||||
|
.map(
|
||||||
|
(key) =>
|
||||||
|
`${encodeURIComponent(key)}=${encodeURIComponent(
|
||||||
|
queryParams[key] as string
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
.sort();
|
||||||
|
canonicalRequest.push(sortedQueryParams.join('&'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Canonical Headers
|
||||||
|
const sortedHeaders = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((key) => `${key.toLowerCase()}:${(headers[key] as string).trim()}`);
|
||||||
|
|
||||||
|
canonicalRequest.push(sortedHeaders.join('\n'));
|
||||||
|
|
||||||
|
// Step 5: Signed Headers
|
||||||
|
const signedHeaders = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((key) => key.toLowerCase())
|
||||||
|
.join(';');
|
||||||
|
canonicalRequest.push(signedHeaders);
|
||||||
|
|
||||||
|
const hashedPayload = hash(payload);
|
||||||
|
canonicalRequest.push(hashedPayload);
|
||||||
|
|
||||||
|
return canonicalRequest.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareStringToSign(
|
||||||
|
datetime: string,
|
||||||
|
credentialScope: string,
|
||||||
|
hashedCanonicalRequest: string
|
||||||
|
) {
|
||||||
|
const stringToSign = [
|
||||||
|
'AWS4-HMAC-SHA256',
|
||||||
|
datetime,
|
||||||
|
credentialScope,
|
||||||
|
hashedCanonicalRequest,
|
||||||
|
];
|
||||||
|
|
||||||
|
return stringToSign.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSigningKey(
|
||||||
|
secretKey: string,
|
||||||
|
date: string,
|
||||||
|
region: string,
|
||||||
|
service: string
|
||||||
|
) {
|
||||||
|
const dateKey = hmacWoHex('AWS4' + secretKey, date);
|
||||||
|
const dateRegionKey = hmacWoHex(dateKey, region);
|
||||||
|
const dateRegionServiceKey = hmacWoHex(dateRegionKey, service);
|
||||||
|
const signingKey = hmacWoHex(dateRegionServiceKey, 'aws4_request');
|
||||||
|
return signingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAuthorizationHeader(
|
||||||
|
accessKey: string,
|
||||||
|
credentialScope: string,
|
||||||
|
signedHeaders: string,
|
||||||
|
signature: string
|
||||||
|
) {
|
||||||
|
return `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
const accessKeyId = $.auth.data.accessKeyId as string;
|
||||||
|
const secretAccessKey = $.auth.data.secretAccessKey as string;
|
||||||
|
const date = getYYYYMMDD();
|
||||||
|
const formattedDate = getISODate();
|
||||||
|
const region = 'us-east-1';
|
||||||
|
const method = 'GET';
|
||||||
|
const path = '/';
|
||||||
|
const queryParams = '';
|
||||||
|
const payload = '';
|
||||||
|
const headers = {
|
||||||
|
Host: 's3.amazonaws.com',
|
||||||
|
'X-Amz-Content-Sha256': hash(payload),
|
||||||
|
'X-Amz-Date': formattedDate,
|
||||||
|
};
|
||||||
|
const headerKeys = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((header) => header.toLowerCase())
|
||||||
|
.join(';');
|
||||||
|
|
||||||
|
const canonicalRequest = prepareCanonicalRequest(
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
queryParams,
|
||||||
|
headers,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
const stringToSign = prepareStringToSign(
|
||||||
|
formattedDate,
|
||||||
|
`${date}/${region}/s3/aws4_request`,
|
||||||
|
hash(canonicalRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
const signingKey = calculateSigningKey(secretAccessKey, date, region, 's3');
|
||||||
|
|
||||||
|
const signature = hmac(signingKey, stringToSign);
|
||||||
|
|
||||||
|
const authorizationHeader = createAuthorizationHeader(
|
||||||
|
accessKeyId,
|
||||||
|
`${date}/${region}/s3/aws4_request`,
|
||||||
|
headerKeys,
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($.auth.data?.secretAccessKey && $.auth.data?.accessKeyId) {
|
||||||
|
requestConfig.headers.Authorization = authorizationHeader;
|
||||||
|
requestConfig.headers['Host'] = 's3.amazonaws.com';
|
||||||
|
requestConfig.headers['X-Amz-Content-Sha256'] = hash(payload);
|
||||||
|
requestConfig.headers['X-Amz-Date'] = formattedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,13 @@
|
|||||||
|
export const getYYYYMMDD = () => {
|
||||||
|
const today = new Date();
|
||||||
|
const year = today.getFullYear();
|
||||||
|
const month = (today.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const day = today.getDate().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
const formattedDate = `${year}${month}${day}`;
|
||||||
|
return formattedDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getISODate = () => {
|
||||||
|
return new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
|
||||||
|
};
|
@@ -1,9 +1,7 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||||
const { data: currentUser } = await $.http.get(
|
const { data: currentUser } = await $.http.get('/');
|
||||||
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
|
||||||
);
|
|
||||||
return currentUser;
|
return currentUser;
|
||||||
};
|
};
|
||||||
|
|
0
packages/backend/src/apps/amazon-s3/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/amazon-s3/index.d.ts
vendored
Normal file
16
packages/backend/src/apps/amazon-s3/index.ts
Normal file
16
packages/backend/src/apps/amazon-s3/index.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Amazon S3',
|
||||||
|
key: 'amazon-s3',
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: 'https://s3.amazonaws.com',
|
||||||
|
iconUrl: '{BASE_URL}/apps/amazon-s3/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/amazon-s3/connection',
|
||||||
|
primaryColor: '7B1D13',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
});
|
@@ -1,5 +1,5 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../common/get-current-user';
|
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
const currentUser = await getCurrentUser($);
|
const currentUser = await getCurrentUser($);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../common/get-current-user';
|
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
||||||
|
|
||||||
type TUser = {
|
type TUser = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
Reference in New Issue
Block a user