Skip to content

Commit

Permalink
SK-243 Device registration api (#125)
Browse files Browse the repository at this point in the history
* SK-243 created an encryption controller, added a device registry method and a test for it

* SK-243 added delete method and test for it

* SK-243 added device list method and test for it

* SK-243 added migration for encryption colection

* SK-243 replaced collection name

* SK-243 added request_keys method and test for it

* SK-243 updated API.md
  • Loading branch information
Oleksandr1414 authored Aug 15, 2024
1 parent 2fe1e56 commit 1094bb5
Show file tree
Hide file tree
Showing 22 changed files with 777 additions and 0 deletions.
45 changes: 45 additions & 0 deletions APIs/JSON/controllers/encryption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import BaseJSONController from "./base.js"

import ServiceLocatorContainer from "@sama/common/ServiceLocatorContainer.js"

import Response from "@sama/networking/models/Response.js"

class EncryptionController extends BaseJSONController {
async register(ws, data) {
const { id: requestId } = data

const encryptionRegisterOperation = ServiceLocatorContainer.use("EncryptionRegisterOperation")
await encryptionRegisterOperation.perform(ws, data.device_register)

return new Response().addBackMessage({ response: { id: requestId, success: true } })
}

async list(ws, data) {
const { id: requestId } = data

const encryptionListOperation = ServiceLocatorContainer.use("EncryptionListOperation")
const deviceList = await encryptionListOperation.perform(ws)

return new Response().addBackMessage({ response: { id: requestId, devices: deviceList } })
}

async request_keys(ws, data) {
const { id: requestId } = data

const encryptionRequestKeysOperation = ServiceLocatorContainer.use("EncryptionRequestKeysOperation")
const deviceList = await encryptionRequestKeysOperation.perform(ws, data.request_keys)

return new Response().addBackMessage({ response: { id: requestId, devices: deviceList } })
}

async delete(ws, data) {
const { id: requestId } = data

const encryptionDeleteOperation = ServiceLocatorContainer.use("EncryptionDeleteOperation")
await encryptionDeleteOperation.perform(ws, data.device_delete)

return new Response().addBackMessage({ response: { id: requestId, success: true } })
}
}

export default new EncryptionController()
18 changes: 18 additions & 0 deletions APIs/JSON/routes/routes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { default as ContactsController } from "../controllers/contacts.js"
import { default as ConversationsController } from "../controllers/conversations.js"
import { default as EncryptionController } from "../controllers/encryption.js"
import { default as FilesController } from "../controllers/files.js"
import { default as LastActivityiesController } from "../controllers/activities.js"
import { default as MessagesController } from "../controllers/messages.js"
Expand All @@ -14,6 +15,7 @@ import authGuardMiddleware from "../middleware/auth_guard.js"
import { activitiesSchemaValidation } from "../validations/activities_schema_validation.js"
import { contactsSchemaValidation } from "../validations/contacts_schema_validation.js"
import { conversationsSchemaValidation } from "../validations/conversations_schema_validation.js"
import { encryptionSchemaValidation } from "../validations/encryption_schema_validation.js"
import { filesSchemaValidation } from "../validations/files_schema_validation.js"
import { messagesSchemaValidation } from "../validations/messages_schema_validation.js"
import { operationsLogSchemaValidation } from "../validations/operations_log_schema_validation.js"
Expand Down Expand Up @@ -181,4 +183,20 @@ export const routes = {
PushNotificationsController.middleware(authGuardMiddleware, ws, json)
.validate(json.push_event_create, pushNotificationsSchemaValidation.push_event_create)
.push_event_create(ws, json),
device_register: (ws, json) =>
EncryptionController.middleware(authGuardMiddleware, ws, json)
.validate(json.device_register, encryptionSchemaValidation.device_register)
.register(ws, json),
device_list: (ws, json) =>
EncryptionController.middleware(authGuardMiddleware, ws, json)
.validate(json.device_list, encryptionSchemaValidation.device_list)
.list(ws, json),
request_keys: (ws, json) =>
EncryptionController.middleware(authGuardMiddleware, ws, json)
.validate(json.request_keys, encryptionSchemaValidation.request_keys)
.request_keys(ws, json),
device_delete: (ws, json) =>
EncryptionController.middleware(authGuardMiddleware, ws, json)
.validate(json.device_delete, encryptionSchemaValidation.device_delete)
.delete(ws, json),
}
68 changes: 68 additions & 0 deletions APIs/JSON/validations/encryption_schema_validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Joi from "joi"
import { ERROR_STATUES } from "@sama/constants/errors.js"

export const encryptionSchemaValidation = {
device_register: Joi.object({
identity_key: Joi.string()
.max(255)
.required()
.error(
new Error(ERROR_STATUES.INCORRECT_IDENTITY_KEY.message, {
cause: ERROR_STATUES.INCORRECT_IDENTITY_KEY,
})
),
signed_key: Joi.string()
.max(255)
.required()
.error(
new Error(ERROR_STATUES.INCORRECT_SIGNED_KEY.message, {
cause: ERROR_STATUES.INCORRECT_SIGNED_KEY,
})
),
one_time_pre_keys: Joi.array()
.items(
Joi.string()
.max(255)
.error(
new Error(ERROR_STATUES.INCORRECT_ONE_TIME_PRE_KEYS.message, {
cause: ERROR_STATUES.INCORRECT_ONE_TIME_PRE_KEYS,
})
)
)
.max(100)
.required()
.error(
new Error(ERROR_STATUES.INCORRECT_ONE_TIME_PRE_KEYS.message, {
cause: ERROR_STATUES.INCORRECT_ONE_TIME_PRE_KEYS,
})
),
}),
device_list: Joi.object({}),
request_keys: Joi.object({
user_ids: Joi.array()
.items(
Joi.string().error(
new Error(ERROR_STATUES.INCORRECT_USER_ID.message, {
cause: ERROR_STATUES.INCORRECT_USER_ID,
})
)
)
.max(50)
.required()
.error(
new Error(ERROR_STATUES.INCORRECT_USERS_ARRAY.message, {
cause: ERROR_STATUES.INCORRECT_USERS_ARRAY,
})
),
}),
device_delete: Joi.object({
key: Joi.string()
.max(255)
.required()
.error(
new Error(ERROR_STATUES.INCORRECT_IDENTITY_KEY.message, {
cause: ERROR_STATUES.INCORRECT_IDENTITY_KEY,
})
),
}),
}
6 changes: 6 additions & 0 deletions app/constants/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ export const ERROR_STATUES = {
message: "Push notification record not found.",
},
INCORRECT_KEYS: { status: 422, message: "Incorrect keys." },
//Encryption -->
INCORRECT_IDENTITY_KEY: { status: 422, message: "Incorrect identity device key." },
INCORRECT_SIGNED_KEY: { status: 422, message: "Incorrect signed key." },
INCORRECT_ONE_TIME_PRE_KEYS: { status: 422, message: "Incorrect one time pre keys." },
INCORRECT_USER_ID: { status: 422, message: "Incorrect user id." },
INCORRECT_USERS_ARRAY: { status: 422, message: "Incorrect users array." },
// Other -->
LOG_TIMETAMP_MISSED: { status: 422, message: "Gt or lt query missed." },
CID_REQUIRED: {
Expand Down
13 changes: 13 additions & 0 deletions app/new_models/encrypted_device.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import BaseModel from "./base.js"

class EncryptedDevice extends BaseModel {
static get collection() {
return "encrypted_devices"
}

static get visibleFields() {
return ["_id", "identity_key", "signed_key", "one_time_pre_keys"]
}
}

export default EncryptedDevice
14 changes: 14 additions & 0 deletions app/providers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ConversationRepoProvider from "./repositories/conversation/Provider.js"
import ConversationParticipantRepoProvider from "./repositories/conversation_participants/Provider.js"
import MessageRepoProvider from "./repositories/message/Provider.js"
import MessageStatusRepoProvider from "./repositories/message_status/Provider.js"
import EncryptionRepoProvider from "./repositories/encryption/Provider.js"

import SessionServiceProvider from "./services/session/Provider.js"
import UserServiceProvider from "./services/user/Provider.js"
Expand All @@ -22,6 +23,7 @@ import ActivityManagerServiceProvider from "./services/activity_manager/Provider
import ConversationServiceProvider from "./services/conversation/Provider.js"
import MessageServiceProvider from "./services/message/Provider.js"
import ConversationNotificationProvider from "./services/conversation_notification/Provider.js"
import EncryptionServiceProvider from "./services/encryption/Provider.js"

import UserAuthOperationProvider from "./operations/user/auth/Provider.js"
import UserLogoutOperationProvider from "./operations/user/logout/Provider.js"
Expand Down Expand Up @@ -56,6 +58,11 @@ import MessageSendSystemOperationProvider from "./operations/message/system/Prov

import StatusTypingOperationProvider from "./operations/status/typing/Provider.js"

import EncryptionRegisterOperationProvider from "./operations/encryption/register/Provider.js"
import EncryptionListOperationProvider from "./operations/encryption/list/Provider.js"
import EncryptionRequestKeysOperationProvider from "./operations/encryption/request_keys/Provider.js"
import EncryptionDeleteOperationProvider from "./operations/encryption/delete/Provider.js"

const providers = [
HelpersProvider,

Expand All @@ -72,6 +79,7 @@ const providers = [
ConversationParticipantRepoProvider,
MessageRepoProvider,
MessageStatusRepoProvider,
EncryptionRepoProvider,

SessionServiceProvider,
UserServiceProvider,
Expand All @@ -81,6 +89,7 @@ const providers = [
ConversationServiceProvider,
MessageServiceProvider,
ConversationNotificationProvider,
EncryptionServiceProvider,

UserAuthOperationProvider,
UserLogoutOperationProvider,
Expand Down Expand Up @@ -114,6 +123,11 @@ const providers = [
MessageSendSystemOperationProvider,

StatusTypingOperationProvider,

EncryptionRegisterOperationProvider,
EncryptionListOperationProvider,
EncryptionRequestKeysOperationProvider,
EncryptionDeleteOperationProvider,
]

export default providers
19 changes: 19 additions & 0 deletions app/providers/operations/encryption/delete/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import RegisterProvider from "../../../../common/RegisterProvider.js"
import EncryptionDeleteOperation from "./index.js"

const name = "EncryptionDeleteOperation"

class EncryptionDeleteOperationRegisterProvider extends RegisterProvider {
register(slc) {
const encryptionService = slc.use("EncryptionService")
const sessionService = slc.use("SessionService")
const helpers = slc.use("Helpers")

return new EncryptionDeleteOperation(encryptionService, sessionService, helpers)
}
}

export default new EncryptionDeleteOperationRegisterProvider({
name,
implementationName: EncryptionDeleteOperation.name,
})
25 changes: 25 additions & 0 deletions app/providers/operations/encryption/delete/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ERROR_STATUES } from "../../../../constants/errors.js"

class EncryptionDeleteOperation {
constructor(encryptionService, sessionService, helpers) {
this.encryptionService = encryptionService
this.sessionService = sessionService
this.helpers = helpers
}

async perform(ws, deleteParams) {
const device = await this.encryptionService.encryptionRepo.findByIdentityKey(deleteParams.key)

const userId = this.sessionService.getSessionUserId(ws)

if (!this.helpers.isEqualsNativeIds(device.user_id, userId)) {
throw new Error(ERROR_STATUES.FORBIDDEN.message, {
cause: ERROR_STATUES.FORBIDDEN,
})
}

await this.encryptionService.encryptionRepo.deleteById(device._id)
}
}

export default EncryptionDeleteOperation
18 changes: 18 additions & 0 deletions app/providers/operations/encryption/list/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import RegisterProvider from "../../../../common/RegisterProvider.js"
import EncryptionListOperation from "./index.js"

const name = "EncryptionListOperation"

class EncryptionListOperationRegisterProvider extends RegisterProvider {
register(slc) {
const encryptionService = slc.use("EncryptionService")
const sessionService = slc.use("SessionService")

return new EncryptionListOperation(encryptionService, sessionService)
}
}

export default new EncryptionListOperationRegisterProvider({
name,
implementationName: EncryptionListOperation.name,
})
15 changes: 15 additions & 0 deletions app/providers/operations/encryption/list/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class EncryptionListOperation {
constructor(encryptionService, sessionService) {
this.encryptionService = encryptionService
this.sessionService = sessionService
}

async perform(ws) {
const userId = this.sessionService.getSessionUserId(ws)

const deviceList = await this.encryptionService.encryptionRepo.findAll({ user_id: userId })
return deviceList.map((device) => ({ identity_key: device.identity_key, signed_key: device.signed_key }))
}
}

export default EncryptionListOperation
18 changes: 18 additions & 0 deletions app/providers/operations/encryption/register/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import RegisterProvider from "../../../../common/RegisterProvider.js"
import EncryptionRegisterOperation from "./index.js"

const name = "EncryptionRegisterOperation"

class EncryptionRegisterOperationProvider extends RegisterProvider {
register(slc) {
const encryptionService = slc.use("EncryptionService")
const sessionService = slc.use("SessionService")

return new EncryptionRegisterOperation(encryptionService, sessionService)
}
}

export default new EncryptionRegisterOperationProvider({
name,
implementationName: EncryptionRegisterOperation.name,
})
21 changes: 21 additions & 0 deletions app/providers/operations/encryption/register/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class EncryptionRegisterOperation {
constructor(encryptionService, sessionService) {
this.encryptionService = encryptionService
this.sessionService = sessionService
}

async perform(ws, registerDeviceParams) {
const existingDevice = await this.encryptionService.encryptionRepo.findByIdentityKey(
registerDeviceParams.identity_key
)

if (existingDevice) {
await this.encryptionService.update(existingDevice, registerDeviceParams)
} else {
const currentuserId = this.sessionService.getSessionUserId(ws)
await this.encryptionService.encryptionRepo.create({ user_id: currentuserId, ...registerDeviceParams })
}
}
}

export default EncryptionRegisterOperation
18 changes: 18 additions & 0 deletions app/providers/operations/encryption/request_keys/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import RegisterProvider from "../../../../common/RegisterProvider.js"
import EncryptionRequestKeysOperation from "./index.js"

const name = "EncryptionRequestKeysOperation"

class EncryptionRequestKeysOperationProvider extends RegisterProvider {
register(slc) {
const userRepo = slc.use("UserRepository")
const encryptionService = slc.use("EncryptionService")

return new EncryptionRequestKeysOperation(encryptionService, userRepo)
}
}

export default new EncryptionRequestKeysOperationProvider({
name,
implementationName: EncryptionRequestKeysOperation.name,
})
20 changes: 20 additions & 0 deletions app/providers/operations/encryption/request_keys/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class EncryptionRequestKeysOperation {
constructor(encryptionService, userRepo) {
this.encryptionService = encryptionService
this.userRepo = userRepo
}

async perform(ws, listParams) {
const userIds = listParams.user_ids

const existUserIds = await this.userRepo.retrieveExistedIds(userIds)

const deviceList = await this.encryptionService.encryptionRepo.getAllUserDevicesByIds(existUserIds)

await this.encryptionService.removeFirstOneTimeKey(userIds)

return deviceList
}
}

export default EncryptionRequestKeysOperation
Loading

0 comments on commit 1094bb5

Please sign in to comment.