ag-common 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/helpers/index.d.ts +2 -0
- package/dist/api/helpers/index.js +2 -0
- package/dist/api/helpers/validateOpenApi.d.ts +14 -0
- package/dist/api/helpers/validateOpenApi.js +114 -0
- package/dist/api/helpers/validations.d.ts +8 -0
- package/dist/api/helpers/validations.js +116 -0
- package/package.json +6 -2
|
@@ -13,3 +13,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
__exportStar(require("./api"), exports);
|
|
14
14
|
__exportStar(require("./dynamoInfra"), exports);
|
|
15
15
|
__exportStar(require("./openApiHelpers"), exports);
|
|
16
|
+
__exportStar(require("./validateOpenApi"), exports);
|
|
17
|
+
__exportStar(require("./validations"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda';
|
|
2
|
+
import { User } from 'analytica.click';
|
|
3
|
+
export declare type NextType<T> = ({ event, body, params, userProfile, }: {
|
|
4
|
+
params: Record<string, string>;
|
|
5
|
+
event: APIGatewayEvent;
|
|
6
|
+
body: T;
|
|
7
|
+
userProfile?: User;
|
|
8
|
+
}) => Promise<APIGatewayProxyResult>;
|
|
9
|
+
export declare function validateOpenApi<T>({ event, next, authorized, schema, }: {
|
|
10
|
+
schema: any;
|
|
11
|
+
event: APIGatewayEvent;
|
|
12
|
+
next: NextType<T>;
|
|
13
|
+
authorized?: true | false | 'optional';
|
|
14
|
+
}): Promise<APIGatewayProxyResult>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.validateOpenApi = void 0;
|
|
16
|
+
const openapi_request_validator_1 = __importDefault(require("openapi-request-validator"));
|
|
17
|
+
const validations_1 = require("./validations");
|
|
18
|
+
const log_1 = require("../../common/helpers/log");
|
|
19
|
+
const object_1 = require("../../common/helpers/object");
|
|
20
|
+
const api_1 = require("./api");
|
|
21
|
+
//
|
|
22
|
+
const getOperation = ({ path, method, resource, schema, }) => {
|
|
23
|
+
var _a;
|
|
24
|
+
const resourcePath = Object.keys(schema.paths).find((rp) => rp === resource);
|
|
25
|
+
if (!resourcePath) {
|
|
26
|
+
throw new Error('incorrect path');
|
|
27
|
+
}
|
|
28
|
+
const operation = (_a = schema.paths[resourcePath]) === null || _a === void 0 ? void 0 : _a[method];
|
|
29
|
+
if (!operation) {
|
|
30
|
+
const msg = `no operation found for ${method}/${path}`;
|
|
31
|
+
(0, log_1.warn)(`${msg} ${Object.keys(schema.paths)}`);
|
|
32
|
+
throw new Error(msg);
|
|
33
|
+
}
|
|
34
|
+
/*
|
|
35
|
+
var path= '/events/12345/topics/2216026039415263/like'
|
|
36
|
+
var resourcePath ='/events/{eventCode}/topics/{topicId}/like'
|
|
37
|
+
*/
|
|
38
|
+
const re = new RegExp(resourcePath
|
|
39
|
+
.replace(/\//gim, `\\/`)
|
|
40
|
+
.replace(/\{(.+?)\}/gim, '(?<$1>[^\\\\]+)'), 'i').exec(path);
|
|
41
|
+
const pathParams = (re === null || re === void 0 ? void 0 : re.groups) && JSON.parse(JSON.stringify(re === null || re === void 0 ? void 0 : re.groups));
|
|
42
|
+
return { operation, pathParams };
|
|
43
|
+
};
|
|
44
|
+
function validateOpenApi({ event, next, authorized, schema, }) {
|
|
45
|
+
var _a, _b, _c, _d;
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const request = {
|
|
48
|
+
method: event.httpMethod,
|
|
49
|
+
path: event.path,
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
params: undefined,
|
|
52
|
+
query: event.queryStringParameters,
|
|
53
|
+
body: event.body && JSON.parse(event.body),
|
|
54
|
+
headers: (0, object_1.objectKeysToLowerCase)(event === null || event === void 0 ? void 0 : event.headers),
|
|
55
|
+
};
|
|
56
|
+
const method = event.requestContext.httpMethod.toLowerCase();
|
|
57
|
+
const pathParameters = event.pathParameters || {};
|
|
58
|
+
const queryStringParameters = event.queryStringParameters || {};
|
|
59
|
+
//
|
|
60
|
+
const opm = getOperation({
|
|
61
|
+
path: event.path,
|
|
62
|
+
method,
|
|
63
|
+
resource: event.resource,
|
|
64
|
+
schema,
|
|
65
|
+
});
|
|
66
|
+
if (!(opm === null || opm === void 0 ? void 0 : opm.operation)) {
|
|
67
|
+
const msg = `no request handler found! for ${method} ${event.path} - cant validate`;
|
|
68
|
+
(0, log_1.error)(msg);
|
|
69
|
+
return (0, api_1.returnCode)(400, msg);
|
|
70
|
+
}
|
|
71
|
+
if (!opm.operation.requestBody && !opm.operation.parameters) {
|
|
72
|
+
if (!!event.body || Object.keys(pathParameters).length > 0) {
|
|
73
|
+
(0, log_1.warn)(`bad req, unexpected params`);
|
|
74
|
+
return (0, api_1.returnCode)(400, 'bad data');
|
|
75
|
+
}
|
|
76
|
+
// no validation necessary
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
try {
|
|
80
|
+
request.params = opm.pathParams;
|
|
81
|
+
(0, log_1.info)('req=', JSON.stringify(request, null, 2));
|
|
82
|
+
const resp = new openapi_request_validator_1.default(Object.assign(Object.assign({}, opm.operation), { schemas: schema.components.schemas })).validateRequest(request);
|
|
83
|
+
if (resp) {
|
|
84
|
+
(0, log_1.warn)('bad request');
|
|
85
|
+
(0, log_1.warn)('opm=', JSON.stringify(opm, null, 2));
|
|
86
|
+
(0, log_1.warn)('resp=', JSON.stringify(resp, null, 2));
|
|
87
|
+
return (0, api_1.returnCode)(400, `error:${(_b = (_a = resp === null || resp === void 0 ? void 0 : resp.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message}`);
|
|
88
|
+
}
|
|
89
|
+
(0, log_1.debug)(`validated request:`, event.path);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
(0, log_1.error)('e=', e, JSON.stringify(opm));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let userProfile;
|
|
96
|
+
let error;
|
|
97
|
+
const authHeader = ((_c = event.headers) === null || _c === void 0 ? void 0 : _c.Authorization) || ((_d = event.headers) === null || _d === void 0 ? void 0 : _d.authorization);
|
|
98
|
+
if (authorized === true || (authorized === 'optional' && authHeader)) {
|
|
99
|
+
({ error, userProfile } = yield (0, validations_1.getAndValidateToken)(authHeader));
|
|
100
|
+
if (error) {
|
|
101
|
+
return error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const params = Object.assign(Object.assign({}, (pathParameters || {})), (queryStringParameters || {}));
|
|
105
|
+
const res = yield next({
|
|
106
|
+
params,
|
|
107
|
+
event,
|
|
108
|
+
body: (event.body && JSON.parse(event.body)),
|
|
109
|
+
userProfile,
|
|
110
|
+
});
|
|
111
|
+
return res;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
exports.validateOpenApi = validateOpenApi;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { APIGatewayProxyResult } from 'aws-lambda';
|
|
2
|
+
import { User } from 'analytica.click';
|
|
3
|
+
import { error } from '../../common/helpers/log';
|
|
4
|
+
export declare const getAndValidateToken: (tokenRaw?: string | undefined) => Promise<{
|
|
5
|
+
error?: APIGatewayProxyResult | undefined;
|
|
6
|
+
token?: string | undefined;
|
|
7
|
+
userProfile?: User | undefined;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.getAndValidateToken = void 0;
|
|
16
|
+
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
|
|
17
|
+
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
18
|
+
const log_1 = require("../../common/helpers/log");
|
|
19
|
+
const api_1 = require("./api");
|
|
20
|
+
const jwksUri = `https://cognito-idp.ap-southeast-2.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}/.well-known/jwks.json`;
|
|
21
|
+
const issuer = `https://cognito-idp.ap-southeast-2.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`;
|
|
22
|
+
const jwksClient = (0, jwks_rsa_1.default)({
|
|
23
|
+
cache: true,
|
|
24
|
+
rateLimit: true,
|
|
25
|
+
jwksRequestsPerMinute: 10,
|
|
26
|
+
jwksUri,
|
|
27
|
+
});
|
|
28
|
+
const jwtVerify = (token) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
(0, jsonwebtoken_1.verify)(token, (header, callback) => {
|
|
31
|
+
jwksClient.getSigningKey(header.kid, (errorV, key) => {
|
|
32
|
+
if (errorV) {
|
|
33
|
+
reject(errorV);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const signingKey = (key === null || key === void 0 ? void 0 : key.publicKey) || (key === null || key === void 0 ? void 0 : key.rsaPublicKey) || undefined;
|
|
37
|
+
if (!signingKey) {
|
|
38
|
+
callback('no key');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
callback(null, signingKey);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, {
|
|
45
|
+
issuer,
|
|
46
|
+
algorithms: ['RS256'],
|
|
47
|
+
}, (errorV, decoded) => {
|
|
48
|
+
if (errorV) {
|
|
49
|
+
reject(errorV);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
resolve(decoded);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const getAndValidateToken = (tokenRaw) => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
let token = '';
|
|
59
|
+
try {
|
|
60
|
+
if (!tokenRaw) {
|
|
61
|
+
(0, log_1.error)('no auth headers');
|
|
62
|
+
return {
|
|
63
|
+
error: (0, api_1.returnCode)(403, 'auth failed'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
token = tokenRaw.substr(tokenRaw.indexOf(' ') + 1);
|
|
67
|
+
let subject;
|
|
68
|
+
try {
|
|
69
|
+
yield jwtVerify(token);
|
|
70
|
+
const decoded = (0, jsonwebtoken_1.decode)(token);
|
|
71
|
+
(0, log_1.debug)(`decoded=${JSON.stringify(decoded)}`);
|
|
72
|
+
subject = decoded === null || decoded === void 0 ? void 0 : decoded.sub;
|
|
73
|
+
if (!subject) {
|
|
74
|
+
const mess = 'user should have responded with subject (sub) field';
|
|
75
|
+
(0, log_1.error)(mess);
|
|
76
|
+
throw new Error(mess);
|
|
77
|
+
}
|
|
78
|
+
let { picture } = decoded;
|
|
79
|
+
if (((_b = (_a = decoded === null || decoded === void 0 ? void 0 : decoded.identities) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.providerName) === 'Facebook') {
|
|
80
|
+
picture = JSON.parse(decoded.picture).data.url;
|
|
81
|
+
}
|
|
82
|
+
const userId = decoded.email.toLowerCase();
|
|
83
|
+
const userProfile = {
|
|
84
|
+
isAdmin: ['andreigec@hotmail.com', 'andreigec@gmail.com'].includes(userId),
|
|
85
|
+
idJwt: decoded,
|
|
86
|
+
userId,
|
|
87
|
+
nickname: decoded.nickname || decoded['cognito:username'],
|
|
88
|
+
fullname: decoded.name || decoded['cognito:username'],
|
|
89
|
+
picture,
|
|
90
|
+
updatedAt: parseInt(`${decoded.auth_time}000`, 10),
|
|
91
|
+
};
|
|
92
|
+
if (!userProfile || !token || !userProfile.userId) {
|
|
93
|
+
(0, log_1.error)('auth fail');
|
|
94
|
+
return {
|
|
95
|
+
error: (0, api_1.returnCode)(403, 'auth fail'),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return { token, userProfile };
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const ex = e;
|
|
102
|
+
// expiry is too common to log
|
|
103
|
+
if (ex.toString().indexOf('jwt expired') !== -1) {
|
|
104
|
+
(0, log_1.info)(`jwt fail:${e}`);
|
|
105
|
+
}
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
(0, log_1.error)('auth error', e);
|
|
111
|
+
return {
|
|
112
|
+
error: (0, api_1.returnCode)(403, 'auth fail'),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
exports.getAndValidateToken = getAndValidateToken;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ag-common",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
"react-dom": "*",
|
|
23
23
|
"react-hot-toast": "2.1.1",
|
|
24
24
|
"rimraf": "*",
|
|
25
|
-
"typescript-styled-plugin": "*"
|
|
25
|
+
"typescript-styled-plugin": "*",
|
|
26
|
+
"openapi-request-validator": "9.3.1",
|
|
27
|
+
"@types/jsonwebtoken": "8.5.6",
|
|
28
|
+
"jsonwebtoken": "8.5.1",
|
|
29
|
+
"jwks-rsa": "2.0.5"
|
|
26
30
|
},
|
|
27
31
|
"peerDependencies": {
|
|
28
32
|
"aws-lambda": "1.0.7",
|