somod-chat-service 0.3.0 → 0.4.0

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.
@@ -1,2 +1,9 @@
1
- import { Message } from "./types";
1
+ import { Message, MessageInput } from "./types";
2
2
  export declare const putMessage: (tableName: string, userId: string, message: Omit<Message, "seqNo">) => Promise<Message>;
3
+ export declare const validateIncomingMessage: (message: MessageInput, userId: string) => Promise<{
4
+ statusCode: number;
5
+ headers: {
6
+ "Content-Type": string;
7
+ };
8
+ body: string;
9
+ }>;
@@ -1,6 +1,7 @@
1
1
  import { __assign, __awaiter, __generator } from "tslib";
2
2
  import { marshall } from "@aws-sdk/util-dynamodb";
3
3
  import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
4
+ import { threadCache } from "./threadCache";
4
5
  var dynamodb = new DynamoDBClient();
5
6
  export var putMessage = function (tableName, userId, message) { return __awaiter(void 0, void 0, void 0, function () {
6
7
  var msg, messageWithKeys, putItemCommand;
@@ -25,3 +26,38 @@ export var putMessage = function (tableName, userId, message) { return __awaiter
25
26
  }
26
27
  });
27
28
  }); };
29
+ export var validateIncomingMessage = function (message, userId) { return __awaiter(void 0, void 0, void 0, function () {
30
+ var errorMessage, thread;
31
+ return __generator(this, function (_a) {
32
+ switch (_a.label) {
33
+ case 0:
34
+ errorMessage = undefined;
35
+ return [4 /*yield*/, threadCache.get(message.threadId)];
36
+ case 1:
37
+ thread = _a.sent();
38
+ if (thread === undefined) {
39
+ errorMessage = "Invalid threadId : does not exist";
40
+ }
41
+ else if (!thread.participants.includes(userId)) {
42
+ errorMessage = "Invalid threadId : from '".concat(userId, "' is not a participant in thread '").concat(thread.id, "'");
43
+ }
44
+ else if ((message.action == "delete" || message.action == "sessionToken") &&
45
+ message.type != "control") {
46
+ errorMessage = "Invalid type : type must be 'control' for ".concat(message.action, " action");
47
+ }
48
+ else if (message.action == "sessionToken" && !message.sessionToken) {
49
+ errorMessage = "Required 'sessionToken' field when action is 'sessionToken'";
50
+ }
51
+ if (errorMessage) {
52
+ return [2 /*return*/, {
53
+ statusCode: 400,
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({
56
+ message: errorMessage
57
+ })
58
+ }];
59
+ }
60
+ return [2 /*return*/];
61
+ }
62
+ });
63
+ }); };
@@ -0,0 +1,10 @@
1
+ type SessionIdResult = {
2
+ sessionId: string;
3
+ error?: string;
4
+ };
5
+ export declare const Error: {
6
+ required: string;
7
+ invalid: string;
8
+ };
9
+ export declare const handleSessionToken: (threadId: string, sessionToken?: string) => SessionIdResult;
10
+ export {};
@@ -0,0 +1,44 @@
1
+ var _a, _b;
2
+ import { verify } from "jsonwebtoken";
3
+ var sessionJwtSecret = (_a = process.env.SESSION_SECRET) !== null && _a !== void 0 ? _a : "";
4
+ var sessionForce = (_b = process.env.SESSION_FORCE) !== null && _b !== void 0 ? _b : "";
5
+ export var Error = {
6
+ required: "missing required field: sessionToken",
7
+ invalid: "invalid field: sessionToken"
8
+ };
9
+ export var handleSessionToken = function (threadId, sessionToken) {
10
+ var result = { sessionId: "" };
11
+ if (sessionJwtSecret) {
12
+ if (!sessionToken) {
13
+ if (sessionForce == "true") {
14
+ result.error = Error.required;
15
+ }
16
+ }
17
+ else {
18
+ try {
19
+ var session = verify(sessionToken, sessionJwtSecret + "", {
20
+ algorithms: ["HS512"],
21
+ ignoreExpiration: true
22
+ });
23
+ var now = Date.now();
24
+ if (session.threadId == threadId &&
25
+ session.startTime <= now &&
26
+ now <= session.endTime) {
27
+ result.sessionId = session.id;
28
+ }
29
+ else {
30
+ result.error = Error.invalid;
31
+ }
32
+ }
33
+ catch (err) {
34
+ // eslint-disable-next-line no-console
35
+ console.error("session token verification failed. threadId=" +
36
+ threadId +
37
+ ", error=" +
38
+ err);
39
+ result.error = Error.invalid;
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ };
@@ -7,12 +7,20 @@ export type Thread = {
7
7
  export type MessageInput = {
8
8
  threadId: string;
9
9
  type: "text" | "image" | "control";
10
- action: "new" | "edit" | "delete";
10
+ action: "new" | "edit" | "delete" | "sessionToken";
11
11
  message: string;
12
+ sessionToken?: string;
12
13
  };
13
14
  export type Message = {
14
15
  id: string;
15
16
  from: string;
16
17
  seqNo: number;
17
18
  sentAt: number;
19
+ sessionId?: string;
18
20
  } & MessageInput;
21
+ export type Session = {
22
+ id: string;
23
+ threadId: string;
24
+ startTime: number;
25
+ endTime: number;
26
+ };
@@ -1 +1 @@
1
- {"parameters":{"chat.http-api.id":{"type":"string"},"chat.websocket-api.id":{"type":"string"},"chat.enable.websocket":{"type":"boolean","default":true},"dynamodb.pitr.enable":{"type":"boolean","default":false}}}
1
+ {"parameters":{"chat.http-api.id":{"type":"string"},"chat.websocket-api.id":{"type":"string"},"chat.enable.websocket":{"type":"boolean","default":true},"dynamodb.pitr.enable":{"type":"boolean","default":false},"chat.session.jwt.secret":{"type":"string"},"chat.session.force":{"type":"boolean","default":false}}}
@@ -1 +1 @@
1
- {"/post-message":{"POST":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["threadId","type","action","message"],"properties":{"threadId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"type":{"enum":["text","image","control"]},"action":{"enum":["new","edit","delete"]},"message":{"type":"string","maxLength":256}}}}}},"/sync-messages":{"GET":{"parameters":[{"name":"from","in":"query","schema":{"type":"string","pattern":"^[0-9]*$"},"required":false}]}}}
1
+ {"/post-message":{"POST":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["threadId","type","action","message"],"properties":{"threadId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"type":{"enum":["text","image","control"]},"action":{"enum":["new","edit","delete","sessionToken"]},"message":{"type":"string","maxLength":512},"sessionToken":{"type":"string","maxLength":16384}}}}}},"/sync-messages":{"GET":{"parameters":[{"name":"from","in":"query","schema":{"type":"string","pattern":"^[0-9]*$"},"required":false}]}}}
@@ -1,50 +1,41 @@
1
- import { __assign, __awaiter, __generator } from "tslib";
1
+ import { __assign, __awaiter, __generator, __rest } from "tslib";
2
2
  import { RouteBuilder } from "somod-http-extension";
3
3
  import { UserProviderMiddlewareKey } from "../../lib";
4
- import { putMessage } from "../../lib/message";
4
+ import { putMessage, validateIncomingMessage } from "../../lib/message";
5
5
  import { v1 as v1uuid } from "uuid";
6
6
  import { QueryCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
7
7
  import { convertToAttr, unmarshall } from "@aws-sdk/util-dynamodb";
8
- import { threadCache } from "../../lib/threadCache";
8
+ import { handleSessionToken } from "../../lib/sessionUtil";
9
9
  var dynamodb = new DynamoDBClient();
10
10
  var builder = new RouteBuilder();
11
11
  var postMessageHandler = function (request, event) { return __awaiter(void 0, void 0, void 0, function () {
12
- var userId, thread, message;
13
- return __generator(this, function (_a) {
14
- switch (_a.label) {
12
+ var userId, messageValidationError, sessionIdResult, _a, sessionToken, msg, message;
13
+ return __generator(this, function (_b) {
14
+ switch (_b.label) {
15
15
  case 0:
16
16
  userId = event.somodMiddlewareContext.get(UserProviderMiddlewareKey);
17
- return [4 /*yield*/, threadCache.get(request.body.threadId)];
17
+ return [4 /*yield*/, validateIncomingMessage(request.body, userId)];
18
18
  case 1:
19
- thread = _a.sent();
20
- if (thread === undefined) {
21
- return [2 /*return*/, {
22
- statusCode: 400,
23
- headers: { "Content-Type": "application/json" },
24
- body: JSON.stringify({ message: "Invalid threadId : does not exist" })
25
- }];
26
- }
27
- if (!thread.participants.includes(userId)) {
28
- return [2 /*return*/, {
29
- statusCode: 400,
30
- headers: { "Content-Type": "application/json" },
31
- body: JSON.stringify({
32
- message: "Invalid threadId : from '".concat(userId, "' is not a participant in thread '").concat(thread.id, "'")
33
- })
34
- }];
19
+ messageValidationError = _b.sent();
20
+ if (messageValidationError) {
21
+ return [2 /*return*/, messageValidationError];
35
22
  }
36
- if (request.body.action == "delete" && request.body.type != "control") {
23
+ sessionIdResult = handleSessionToken(request.body.threadId, request.body.sessionToken);
24
+ if (sessionIdResult.error) {
37
25
  return [2 /*return*/, {
38
26
  statusCode: 400,
39
27
  headers: { "Content-Type": "application/json" },
40
28
  body: JSON.stringify({
41
- message: "Invalid type : type must be 'control' for 'delete' action"
29
+ message: sessionIdResult.error
42
30
  })
43
31
  }];
44
32
  }
45
- return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign({}, request.body), { id: v1uuid().split("-").join(""), from: userId, sentAt: Date.now() }))];
33
+ _a = request.body, sessionToken = _a.sessionToken, msg = __rest(_a, ["sessionToken"]);
34
+ return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign(__assign({}, msg), { sessionId: sessionIdResult.sessionId, id: v1uuid().split("-").join(""), from: userId, sentAt: Date.now() }), (request.body.action == "sessionToken"
35
+ ? { sessionToken: sessionToken }
36
+ : {})))];
46
37
  case 2:
47
- message = _a.sent();
38
+ message = _b.sent();
48
39
  return [2 /*return*/, {
49
40
  statusCode: 200,
50
41
  headers: { "Content-Type": "application/json" },
@@ -1,43 +1,36 @@
1
1
  import { __assign, __awaiter, __generator, __rest } from "tslib";
2
2
  import { RouteBuilder } from "somod-websocket-extension";
3
- import { threadCache } from "../../lib/threadCache";
4
- import { putMessage } from "../../lib/message";
3
+ import { putMessage, validateIncomingMessage } from "../../lib/message";
5
4
  import { v1 as v1uuid } from "uuid";
6
5
  import { UserProviderMiddlewareKey } from "../../lib";
6
+ import { handleSessionToken } from "../../lib/sessionUtil";
7
7
  var builder = new RouteBuilder();
8
8
  builder.add("$default", function (message, event) { return __awaiter(void 0, void 0, void 0, function () {
9
- var userId, thread, _a, wsMsgId, msg, messageResult;
9
+ var userId, messageValidationError, sessionIdResult, _a, wsMsgId, sessionToken, msg, messageResult;
10
10
  return __generator(this, function (_b) {
11
11
  switch (_b.label) {
12
12
  case 0:
13
13
  userId = event.somodMiddlewareContext.get(UserProviderMiddlewareKey);
14
- return [4 /*yield*/, threadCache.get(message.body.threadId)];
14
+ return [4 /*yield*/, validateIncomingMessage(message.body, userId)];
15
15
  case 1:
16
- thread = _b.sent();
17
- if (thread === undefined) {
18
- return [2 /*return*/, {
19
- statusCode: 400,
20
- headers: { "Content-Type": "application/json" },
21
- body: JSON.stringify({
22
- wsMsgId: message.body.wsMsgId,
23
- type: "error",
24
- message: "Invalid threadId : does not exist"
25
- })
26
- }];
16
+ messageValidationError = _b.sent();
17
+ if (messageValidationError) {
18
+ return [2 /*return*/, messageValidationError];
27
19
  }
28
- if (!thread.participants.includes(userId)) {
20
+ sessionIdResult = handleSessionToken(message.body.threadId, message.body.sessionToken);
21
+ if (sessionIdResult.error) {
29
22
  return [2 /*return*/, {
30
23
  statusCode: 400,
31
24
  headers: { "Content-Type": "application/json" },
32
25
  body: JSON.stringify({
33
- wsMsgId: message.body.wsMsgId,
34
- type: "error",
35
- message: "Invalid threadId : from '".concat(userId, "' is not a participant in thread '").concat(thread.id, "'")
26
+ message: sessionIdResult.error
36
27
  })
37
28
  }];
38
29
  }
39
- _a = message.body, wsMsgId = _a.wsMsgId, msg = __rest(_a, ["wsMsgId"]);
40
- return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign({}, msg), { from: userId, id: v1uuid().split("-").join(""), sentAt: Date.now() }))];
30
+ _a = message.body, wsMsgId = _a.wsMsgId, sessionToken = _a.sessionToken, msg = __rest(_a, ["wsMsgId", "sessionToken"]);
31
+ return [4 /*yield*/, putMessage(process.env.MESSAGE_BOX_TABLE_NAME + "", userId, __assign(__assign(__assign({}, msg), { sessionId: sessionIdResult.sessionId, from: userId, id: v1uuid().split("-").join(""), sentAt: Date.now() }), (message.body.action == "sessionToken"
32
+ ? { sessionToken: sessionToken }
33
+ : {})))];
41
34
  case 2:
42
35
  messageResult = _b.sent();
43
36
  return [2 /*return*/, {
@@ -1 +1 @@
1
- {"$default":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["wsMsgId","threadId","type","action","message"],"properties":{"wsMsgId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"threadId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"type":{"enum":["text","image"]},"action":{"enum":["new","edit","delete"]},"message":{"type":"string","maxLength":256}}}}}}
1
+ {"$default":{"body":{"parser":"json","schema":{"type":"object","additionalProperties":false,"required":["wsMsgId","threadId","type","action","message"],"properties":{"wsMsgId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"threadId":{"type":"string","pattern":"^[a-f0-9]{32}$"},"type":{"enum":["text","image"]},"action":{"enum":["new","edit","delete","sessionToken"]},"message":{"type":"string","maxLength":512},"sessionToken":{"type":"string","maxLength":16384}}}}}}
@@ -1 +1 @@
1
- {"Resources":{"ChatApi":{"Type":"AWS::Serverless::HttpApi","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{}},"ThreadTable":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"Thread"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"}]}},"MessageBox":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn","StreamArn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"MessageBox"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"userId","KeyType":"HASH"},{"AttributeName":"seqNo","KeyType":"RANGE"}],"AttributeDefinitions":[{"AttributeName":"userId","AttributeType":"S"},{"AttributeName":"seqNo","AttributeType":"N"}],"StreamSpecification":{"StreamViewType":"NEW_IMAGE"}}},"MessageStreamHandlerDLQ":{"Type":"AWS::SQS::Queue","SOMOD::Output":{"default":true,"attributes":["Arn","QueueName","QueueUrl"]},"Properties":{"QueueName":{"SOMOD::ResourceName":"MessageStreamHandlerDLQ"}}},"MessageStreamHandler":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MsgStrmHandler"},"CodeUri":{"SOMOD::Function":{"name":"messageStreamHandler","type":"DynamoDB"}},"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"MSG_NOTIFICATION_TOPIC":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}],"Action":["sns:Publish"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}],"Action":["sqs:SendMessage"]}]}],"Events":{"Stream":{"Type":"DynamoDB","Properties":{"Stream":{"SOMOD::Ref":{"resource":"MessageBox","attribute":"StreamArn"}},"BatchSize":1,"MaximumRetryAttempts":3,"StartingPosition":"LATEST","DestinationConfig":{"OnFailure":{"Destination":{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}}}}}}}},"UserProviderMiddleware":{"Type":"SOMOD::Serverless::FunctionMiddleware","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{"CodeUri":{"SOMOD::FunctionMiddleware":{"name":"userProvider"}}}},"CommonLibsLayer":{"Type":"AWS::Serverless::LayerVersion","SOMOD::Output":{"default":true},"Properties":{"RetentionPolicy":"Delete","ContentUri":{"SOMOD::FunctionLayer":{"name":"commonlibs","libraries":["decorated-ajv"]}}}},"Message":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MessageApi"},"CodeUri":{"SOMOD::Function":{"name":"message","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:Query"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}],"Events":{"PostMessage":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/post-message"}},"SyncMessages":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/sync-messages"}}}}},"Thread":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"ThreadApi"},"CodeUri":{"SOMOD::Function":{"name":"thread","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:GetItem"]}]}],"Events":{"CreateThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/thread"}},"GetThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/thread/{id}"}}}}},"MessageNotificationTopic":{"Type":"AWS::SNS::Topic","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["TopicName"]},"Properties":{"TopicName":{"SOMOD::ResourceName":"MsgNotificationTopic"}}},"WebSocketApi":{"Type":"AWS::ApiGatewayV2::Api","SOMOD::Access":"public","Properties":{"Name":{"SOMOD::ResourceName":"ChatWebsocketApi"},"ProtocolType":"WEBSOCKET","RouteSelectionExpression":"$request.body.action"}},"ConnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::Access":"public","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$connect","OperationName":"ConnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"ConnectInteg"}}]]}}},"ConnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Connect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunctionArn}/invocations",{"OnConnectFunctionArn":{"SOMOD::Ref":{"resource":"OnConnectFunction","attribute":"Arn"}}}]}}},"DisconnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$disconnect","OperationName":"DisconnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DisconnectInteg"}}]]}}},"DisconnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Disconnect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunctionArn}/invocations",{"OnDisconnectFunctionArn":{"SOMOD::Ref":{"resource":"OnDisconnectFunction","attribute":"Arn"}}}]}}},"DefaultRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$default","OperationName":"DefaultRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DefaultInteg"}}]]}}},"DefaultRouteResponse":{"Type":"AWS::ApiGatewayV2::RouteResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"RouteId":{"SOMOD::Ref":{"resource":"DefaultRoute"}},"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteResponseKey":"$default"}},"DefaultInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Default Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnMessageFunctionArn}/invocations",{"OnMessageFunctionArn":{"SOMOD::Ref":{"resource":"OnMessageFunction","attribute":"Arn"}}}]}}},"DefaultIntegResponse":{"Type":"AWS::ApiGatewayV2::IntegrationResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"IntegrationId":{"SOMOD::Ref":{"resource":"DefaultInteg"}},"IntegrationResponseKey":"$default"}},"Stage":{"Type":"AWS::ApiGatewayV2::Stage","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"StageName":"$default","ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"AutoDeploy":true}},"ConnectionsTable":{"Type":"AWS::DynamoDB::Table","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"},{"AttributeName":"userId","AttributeType":"S"}],"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"GlobalSecondaryIndexes":[{"IndexName":"ByUserId","KeySchema":[{"AttributeName":"userId","KeyType":"HASH"}],"Projection":{"ProjectionType":"KEYS_ONLY"}}]}},"OnConnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnConnect","middlewares":[{"resource":"UserProviderMiddleware"}]}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]}]}]}},"OnConnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnConnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$connect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnDisconnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnDisconnect"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:DeleteItem"]}]}]}},"OnDisconnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnDisconnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$disconnect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnMessage","middlewares":[{"module":"somod-websocket-extension","resource":"SomodWebSocketMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}]}},"OnMessagePermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnMessageFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$default",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"NotifyMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"CodeUri":{"SOMOD::Function":{"type":"SNS","name":"wsNotifyMessage"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}},"CONNECTIONS_ENDPOINT":{"Fn::Sub":["https://${WebSocketApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Stage":{"SOMOD::Ref":{"resource":"Stage"}}}]}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"Fn::Sub":["${ConnectionsTableArn}/index/ByUserId",{"ConnectionsTableArn":{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}}]}],"Action":["dynamodb:Query"]}]},{"Statement":[{"Effect":"Allow","Action":["execute-api:ManageConnections"],"Resource":[{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}]}]}],"Events":{"NotifyFromSNS":{"Type":"SNS","Properties":{"Topic":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}}},"Timeout":300}}},"Outputs":{"chat.http-api.id":{"Fn::Sub":["https://${apiId}.execute-api.${AWS::Region}.amazonaws.com/",{"apiId":{"SOMOD::Ref":{"resource":"ChatApi"}}}]},"chat.websocket-api.id":{"SOMOD::If":[{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},{"Fn::Sub":["wss://${apiId}.execute-api.${AWS::Region}.amazonaws.com/$default",{"apiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]},null]}}}
1
+ {"Resources":{"ChatApi":{"Type":"AWS::Serverless::HttpApi","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{}},"ThreadTable":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"Thread"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"}]}},"MessageBox":{"Type":"AWS::DynamoDB::Table","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["Arn","StreamArn"]},"Properties":{"TableName":{"SOMOD::ResourceName":"MessageBox"},"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"KeySchema":[{"AttributeName":"userId","KeyType":"HASH"},{"AttributeName":"seqNo","KeyType":"RANGE"}],"AttributeDefinitions":[{"AttributeName":"userId","AttributeType":"S"},{"AttributeName":"seqNo","AttributeType":"N"}],"StreamSpecification":{"StreamViewType":"NEW_IMAGE"}}},"MessageStreamHandlerDLQ":{"Type":"AWS::SQS::Queue","SOMOD::Output":{"default":true,"attributes":["Arn","QueueName","QueueUrl"]},"Properties":{"QueueName":{"SOMOD::ResourceName":"MessageStreamHandlerDLQ"}}},"MessageStreamHandler":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MsgStrmHandler"},"CodeUri":{"SOMOD::Function":{"name":"messageStreamHandler","type":"DynamoDB"}},"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"MSG_NOTIFICATION_TOPIC":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}],"Action":["sns:Publish"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}],"Action":["sqs:SendMessage"]}]}],"Events":{"Stream":{"Type":"DynamoDB","Properties":{"Stream":{"SOMOD::Ref":{"resource":"MessageBox","attribute":"StreamArn"}},"BatchSize":1,"MaximumRetryAttempts":3,"StartingPosition":"LATEST","DestinationConfig":{"OnFailure":{"Destination":{"SOMOD::Ref":{"resource":"MessageStreamHandlerDLQ","attribute":"Arn"}}}}}}}}},"UserProviderMiddleware":{"Type":"SOMOD::Serverless::FunctionMiddleware","SOMOD::Access":"public","SOMOD::Output":{"default":true},"Properties":{"CodeUri":{"SOMOD::FunctionMiddleware":{"name":"userProvider"}}}},"CommonLibsLayer":{"Type":"AWS::Serverless::LayerVersion","SOMOD::Output":{"default":true},"Properties":{"RetentionPolicy":"Delete","ContentUri":{"SOMOD::FunctionLayer":{"name":"commonlibs","libraries":["decorated-ajv"]}}}},"Message":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"MessageApi"},"CodeUri":{"SOMOD::Function":{"name":"message","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:Query"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}],"Events":{"PostMessage":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/post-message"}},"SyncMessages":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/sync-messages"}}}}},"Thread":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"FunctionName":{"SOMOD::ResourceName":"ThreadApi"},"CodeUri":{"SOMOD::Function":{"name":"thread","type":"HttpApi","middlewares":[{"module":"somod-http-extension","resource":"SomodHttpMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem","dynamodb:GetItem"]}]}],"Events":{"CreateThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"POST","Path":"/thread"}},"GetThread":{"Type":"HttpApi","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"ChatApi"}},"Method":"GET","Path":"/thread/{id}"}}}}},"MessageNotificationTopic":{"Type":"AWS::SNS::Topic","SOMOD::Access":"public","SOMOD::Output":{"default":true,"attributes":["TopicName"]},"Properties":{"TopicName":{"SOMOD::ResourceName":"MsgNotificationTopic"}}},"WebSocketApi":{"Type":"AWS::ApiGatewayV2::Api","SOMOD::Access":"public","Properties":{"Name":{"SOMOD::ResourceName":"ChatWebsocketApi"},"ProtocolType":"WEBSOCKET","RouteSelectionExpression":"$request.body.action"}},"ConnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::Access":"public","Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$connect","OperationName":"ConnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"ConnectInteg"}}]]}}},"ConnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Connect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunctionArn}/invocations",{"OnConnectFunctionArn":{"SOMOD::Ref":{"resource":"OnConnectFunction","attribute":"Arn"}}}]}}},"DisconnectRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$disconnect","OperationName":"DisconnectRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DisconnectInteg"}}]]}}},"DisconnectInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Disconnect Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunctionArn}/invocations",{"OnDisconnectFunctionArn":{"SOMOD::Ref":{"resource":"OnDisconnectFunction","attribute":"Arn"}}}]}}},"DefaultRoute":{"Type":"AWS::ApiGatewayV2::Route","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteKey":"$default","OperationName":"DefaultRoute","Target":{"Fn::Join":["/",["integrations",{"SOMOD::Ref":{"resource":"DefaultInteg"}}]]}}},"DefaultRouteResponse":{"Type":"AWS::ApiGatewayV2::RouteResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"RouteId":{"SOMOD::Ref":{"resource":"DefaultRoute"}},"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"RouteResponseKey":"$default"}},"DefaultInteg":{"Type":"AWS::ApiGatewayV2::Integration","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Description":"Default Integration","IntegrationType":"AWS_PROXY","IntegrationUri":{"Fn::Sub":["arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnMessageFunctionArn}/invocations",{"OnMessageFunctionArn":{"SOMOD::Ref":{"resource":"OnMessageFunction","attribute":"Arn"}}}]}}},"DefaultIntegResponse":{"Type":"AWS::ApiGatewayV2::IntegrationResponse","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"IntegrationId":{"SOMOD::Ref":{"resource":"DefaultInteg"}},"IntegrationResponseKey":"$default"}},"Stage":{"Type":"AWS::ApiGatewayV2::Stage","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"StageName":"$default","ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"AutoDeploy":true}},"ConnectionsTable":{"Type":"AWS::DynamoDB::Table","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"default":true,"attributes":["Arn"]},"Properties":{"BillingMode":"PAY_PER_REQUEST","PointInTimeRecoverySpecification":{"PointInTimeRecoveryEnabled":{"SOMOD::Parameter":"dynamodb.pitr.enable"}},"AttributeDefinitions":[{"AttributeName":"id","AttributeType":"S"},{"AttributeName":"userId","AttributeType":"S"}],"KeySchema":[{"AttributeName":"id","KeyType":"HASH"}],"GlobalSecondaryIndexes":[{"IndexName":"ByUserId","KeySchema":[{"AttributeName":"userId","KeyType":"HASH"}],"Projection":{"ProjectionType":"KEYS_ONLY"}}]}},"OnConnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnConnect","middlewares":[{"resource":"UserProviderMiddleware"}]}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]}]}]}},"OnConnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnConnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$connect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnDisconnectFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnDisconnect"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}],"Action":["dynamodb:DeleteItem"]}]}]}},"OnDisconnectPermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnDisconnectFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$disconnect",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"OnMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"SOMOD::Output":{"attributes":["Arn"]},"Properties":{"CodeUri":{"SOMOD::Function":{"type":"WebSocket","name":"wsOnMessage","middlewares":[{"module":"somod-websocket-extension","resource":"SomodWebSocketMiddleware"},{"resource":"UserProviderMiddleware"}]}},"Layers":[{"SOMOD::Ref":{"resource":"CommonLibsLayer"}}],"Environment":{"Variables":{"MESSAGE_BOX_TABLE_NAME":{"SOMOD::Ref":{"resource":"MessageBox"}},"THREAD_TABLE_NAME":{"SOMOD::Ref":{"resource":"ThreadTable"}},"SESSION_SECRET":{"SOMOD::Parameter":"chat.session.jwt.secret"},"SESSION_FORCE":{"SOMOD::Parameter":"chat.session.force"}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"MessageBox","attribute":"Arn"}}],"Action":["dynamodb:PutItem"]},{"Effect":"Allow","Resource":[{"SOMOD::Ref":{"resource":"ThreadTable","attribute":"Arn"}}],"Action":["dynamodb:GetItem"]}]}]}},"OnMessagePermission":{"Type":"AWS::Lambda::Permission","SOMOD::CreateIf":{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},"Properties":{"Action":"lambda:InvokeFunction","FunctionName":{"SOMOD::Ref":{"resource":"OnMessageFunction"}},"Principal":"apigateway.amazonaws.com","SourceArn":{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/$default/$default",{"ApiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}}},"NotifyMessageFunction":{"Type":"AWS::Serverless::Function","SOMOD::Access":"public","Properties":{"CodeUri":{"SOMOD::Function":{"type":"SNS","name":"wsNotifyMessage"}},"Environment":{"Variables":{"CONNECTIONS_TABLE_NAME":{"SOMOD::Ref":{"resource":"ConnectionsTable"}},"CONNECTIONS_ENDPOINT":{"Fn::Sub":["https://${WebSocketApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}},"Stage":{"SOMOD::Ref":{"resource":"Stage"}}}]}}},"Policies":[{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Resource":[{"Fn::Sub":["${ConnectionsTableArn}/index/ByUserId",{"ConnectionsTableArn":{"SOMOD::Ref":{"resource":"ConnectionsTable","attribute":"Arn"}}}]}],"Action":["dynamodb:Query"]}]},{"Statement":[{"Effect":"Allow","Action":["execute-api:ManageConnections"],"Resource":[{"Fn::Sub":["arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*",{"WebSocketApi":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]}]}]}],"Events":{"NotifyFromSNS":{"Type":"SNS","Properties":{"Topic":{"SOMOD::Ref":{"resource":"MessageNotificationTopic"}}}}},"Timeout":300}}},"Outputs":{"chat.http-api.id":{"Fn::Sub":["https://${apiId}.execute-api.${AWS::Region}.amazonaws.com/",{"apiId":{"SOMOD::Ref":{"resource":"ChatApi"}}}]},"chat.websocket-api.id":{"SOMOD::If":[{"SOMOD::Equals":[{"SOMOD::Parameter":"chat.enable.websocket"},true]},{"Fn::Sub":["wss://${apiId}.execute-api.${AWS::Region}.amazonaws.com/$default",{"apiId":{"SOMOD::Ref":{"resource":"WebSocketApi"}}}]},null]}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "somod-chat-service",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Serverless Chat Service from SOMOD",
5
5
  "scripts": {
6
6
  "prettier": "npx prettier --check --ignore-unknown --no-error-on-unmatched-pattern ./**/*",
@@ -25,6 +25,7 @@
25
25
  "license": "MIT",
26
26
  "devDependencies": {
27
27
  "@types/aws-lambda": "^8.10.130",
28
+ "@types/jsonwebtoken": "^9.0.7",
28
29
  "@types/uuid": "^9.0.7",
29
30
  "decorated-ajv": "^1.1.0",
30
31
  "eslint-config-sodaru": "^1.0.1",
@@ -50,6 +51,7 @@
50
51
  "@aws-sdk/client-dynamodb": "^3.465.0",
51
52
  "@aws-sdk/client-sns": "^3.465.0",
52
53
  "@aws-sdk/util-dynamodb": "^3.465.0",
54
+ "jsonwebtoken": "^9.0.2",
53
55
  "somod-http-extension": "^1.2.3",
54
56
  "somod-websocket-extension": "^1.0.0",
55
57
  "uuid": "^9.0.1"