From 9de7466b22d758e4c7e89ef2774a5a2e11e6a1f7 Mon Sep 17 00:00:00 2001 From: cu Date: Fri, 30 Oct 2020 16:59:15 +0300 Subject: [PATCH 1/6] [WIP] CAES-1443 --- packages/common/api.js | 4 ++-- packages/common/sagas/common/invite.js | 2 +- packages/common/sagas/entities/item.js | 2 +- packages/common/sagas/entities/member.js | 7 ++----- packages/common/sagas/entities/team.js | 4 ++-- packages/common/utils/cipherUtils.js | 5 ----- packages/common/utils/item.js | 3 +++ packages/containers/Bootstrap/Bootstrap.js | 6 +++--- packages/containers/Team/Team.js | 2 +- packages/containers/Team/createColumns.js | 6 +++--- packages/containers/TeamList/TeamList.js | 4 ++-- packages/web-app/pages/invite.js | 2 +- 12 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/common/api.js b/packages/common/api.js index 70eadf834..cb85de263 100755 --- a/packages/common/api.js +++ b/packages/common/api.js @@ -143,9 +143,9 @@ export const deleteTeam = teamId => callApi.delete(`/teams/${teamId}`); export const getTeamMembers = teamId => callApi.get(`/teams/${teamId}/members`); export const getDefaultTeamMembers = () => callApi.get('/teams/default/members'); -export const postAddTeamMember = ({ teamId, userId, role, secret }) => +export const postAddTeamMember = ({ teamId, userId, teamRole, secret }) => callApi.post(`/teams/${teamId}/members/${userId}`, { - teamRole: role, + teamRole, secret, }); export const updateTeamMember = ({ teamId, userId, teamRole }) => diff --git a/packages/common/sagas/common/invite.js b/packages/common/sagas/common/invite.js index 8cea33642..909b68666 100644 --- a/packages/common/sagas/common/invite.js +++ b/packages/common/sagas/common/invite.js @@ -11,7 +11,7 @@ export function* inviteNewMemberBatchSaga({ payload: { members } }) { objectToBase64({ e: email, p: password, - mp: masterPassword, + m: masterPassword, }), ), })); diff --git a/packages/common/sagas/entities/item.js b/packages/common/sagas/entities/item.js index 21f03ce12..5f1695ce8 100644 --- a/packages/common/sagas/entities/item.js +++ b/packages/common/sagas/entities/item.js @@ -611,7 +611,7 @@ export function* createItemSaga({ (!teamId && currentTeamId === TEAM_TYPE.PERSONAL)) && !isSystemItem ) { - yield put(addItemIdsToList([savedItem.id])); + yield put(addItemIdsToList([savedItem.id], savedItem.listId)); } yield put(setCurrentTeamId(teamId || TEAM_TYPE.PERSONAL)); diff --git a/packages/common/sagas/entities/member.js b/packages/common/sagas/entities/member.js index 3b54109c3..c4a3be5d8 100644 --- a/packages/common/sagas/entities/member.js +++ b/packages/common/sagas/entities/member.js @@ -239,7 +239,7 @@ export function* getOrCreateMemberBatchSaga({ payload: { emailRolePairs } }) { } } -export function* addMemberToTeamListsBatchSaga({ payload: { teamId, users } }) { +export function* addTeamMembersBatchSaga({ payload: { teamId, users } }) { try { const keypair = yield select(teamKeyPairSelector, { teamId }); const userIds = users.map(user => user.id); @@ -330,10 +330,7 @@ export default function* memberSagas() { yield takeLatest(CREATE_MEMBER_REQUEST, createMemberSaga); yield takeLatest(CREATE_MEMBER_BATCH_REQUEST, createMemberBatchSaga); yield takeLatest(FETCH_TEAM_MEMBERS_REQUEST, fetchTeamMembersSaga); - yield takeLatest( - ADD_TEAM_MEMBERS_BATCH_REQUEST, - addMemberToTeamListsBatchSaga, - ); + yield takeLatest(ADD_TEAM_MEMBERS_BATCH_REQUEST, addTeamMembersBatchSaga); yield takeLatest(UPDATE_TEAM_MEMBER_ROLE_REQUEST, updateTeamMemberRoleSaga); yield takeLatest(REMOVE_TEAM_MEMBER_REQUEST, removeTeamMemberSaga); } diff --git a/packages/common/sagas/entities/team.js b/packages/common/sagas/entities/team.js index c5b4e4b5c..b10082f34 100644 --- a/packages/common/sagas/entities/team.js +++ b/packages/common/sagas/entities/team.js @@ -43,7 +43,7 @@ import { pinTeam, } from '@caesar/common/api'; import { fetchUsersSaga } from '@caesar/common/sagas/entities/user'; -import { addMemberToTeamListsBatchSaga } from '@caesar/common/sagas/entities/member'; +import { addTeamMembersBatchSaga } from '@caesar/common/sagas/entities/member'; import { getServerErrorMessage, getServerErrors, @@ -274,7 +274,7 @@ export function* createTeamSaga({ ); if (adminsToInvite.length > 0 && serverTeam?.id) { - yield call(addMemberToTeamListsBatchSaga, { + yield call(addTeamMembersBatchSaga, { payload: { teamId: serverTeam?.id, users: adminsToInvite, diff --git a/packages/common/utils/cipherUtils.js b/packages/common/utils/cipherUtils.js index 6029e28f5..340f5031d 100644 --- a/packages/common/utils/cipherUtils.js +++ b/packages/common/utils/cipherUtils.js @@ -1,7 +1,5 @@ import * as openpgp from 'openpgp'; import { generateKeys } from './key'; -import { getHostName } from './getDomainName'; -import { randomId } from './uuid4'; import { objectToBase64, base64ToObject } from './base64'; import { createSrp } from './srp'; import { passwordGenerator } from './passwordGenerator'; @@ -112,9 +110,6 @@ export const generateUsersBatch = async emails => { })); }; -export const generateAnonymousEmail = () => - `anonymous_${randomId()}@${getHostName()}`; - export const generateSeedAndVerifier = (email, password) => { const seed = srp.getRandomSeed(); const verifier = srp.generateV(srp.generateX(seed, email, password)); diff --git a/packages/common/utils/item.js b/packages/common/utils/item.js index b766c67e3..93b2374a8 100644 --- a/packages/common/utils/item.js +++ b/packages/common/utils/item.js @@ -55,6 +55,9 @@ export const generateSystemItemEmail = entityName => { return `systems+${entityName}@${DOMAIN_HOSTNAME || getHostName()}`; }; +export const generateAnonymousEmail = itemId => + `anonymous+${itemId}@${DOMAIN_HOSTNAME || getHostName()}`; + export const extractKeysFromSystemItem = item => { const itemRaws = item.data?.raws || { publicKey: null, diff --git a/packages/containers/Bootstrap/Bootstrap.js b/packages/containers/Bootstrap/Bootstrap.js index c1db7f0cb..f1d12bfad 100644 --- a/packages/containers/Bootstrap/Bootstrap.js +++ b/packages/containers/Bootstrap/Bootstrap.js @@ -62,7 +62,7 @@ class Bootstrap extends Component { currentUser?.domainRoles?.includes(DOMAIN_ROLES.ROLE_ANONYMOUS_USER) || currentUser?.domainRoles?.includes(DOMAIN_ROLES.ROLE_READ_ONLY_USER); - if (!currentUser || (isAnonymousOrReadOnlyUser && !shared?.mp)) { + if (!currentUser || (isAnonymousOrReadOnlyUser && !shared?.m)) { logout(); } @@ -216,7 +216,7 @@ class Bootstrap extends Component { initialStep={currentStep} navigationSteps={this.navigationPanelSteps} currentUser={this.currentUser} - sharedMasterPassword={shared.mp} + sharedMasterPassword={shared.m} masterPassword={IS_PROD ? null : this.props.masterPassword} onFinish={this.handleFinishMasterPassword} /> @@ -225,7 +225,7 @@ class Bootstrap extends Component { // if user is using sharing url and master password is included inside share // url we don't turn on LockScreen via SessionChecker(onFinishTimeout) - if (currentStep === BOOTSTRAP_FINISH && shared.mp) { + if (currentStep === BOOTSTRAP_FINISH && shared.m) { return ( { const measuredRef = useCallback(node => { if (node !== null) { setTableWidth(node.getBoundingClientRect().width); - // To calculate where roleDropdown must be opened + // To calculate where teamRoleDropdown must be opened setTableRowGroupNode(node.children[0]?.children[1].children[0]); } }, []); diff --git a/packages/containers/Team/createColumns.js b/packages/containers/Team/createColumns.js index 808b2aea6..43491e862 100644 --- a/packages/containers/Team/createColumns.js +++ b/packages/containers/Team/createColumns.js @@ -103,7 +103,7 @@ export const createColumns = ({ Cell: ({ value }) => {value}, }; - const roleColumn = { + const teamRoleColumn = { accessor: 'teamRole', width: ROLE_COLUMN_WIDTH, Filter: getColumnFilter('Role'), @@ -129,7 +129,7 @@ export const createColumns = ({ { + handleChangeMemberRole = (member, teamRole) => { // TODO: Need to implement UI. Use member.id instead of userId - this.props.updateTeamMemberRoleRequest(member.id, role); + this.props.updateTeamMemberRoleRequest(member.id, teamRole); }; handlePinTeam = (teamId, isPinned) => event => { diff --git a/packages/web-app/pages/invite.js b/packages/web-app/pages/invite.js index 304c79e29..c93474423 100644 --- a/packages/web-app/pages/invite.js +++ b/packages/web-app/pages/invite.js @@ -4,7 +4,7 @@ import { Bootstrap, Invite } from '@caesar/containers'; import { base64ToObject } from '@caesar/common/utils/base64'; import { login } from '@caesar/common/utils/authUtils'; -const validFields = ['e', 'p', 'mp']; +const validFields = ['e', 'p', 'm']; const validateFields = (data, fields) => data && fields.every(field => !!data[field]); From 1132e3473e94967e8bd9b1c3a16e87320f5baf03 Mon Sep 17 00:00:00 2001 From: cu Date: Mon, 2 Nov 2020 15:59:46 +0300 Subject: [PATCH 2/6] [WIP] CAES-1443 --- packages/common/actions/entities/item.js | 4 +- packages/common/actions/entities/member.js | 9 ++-- packages/common/actions/entities/user.js | 20 +++++++++ packages/common/reducers/entities/item.js | 2 +- packages/common/reducers/entities/user.js | 18 ++++++++ packages/common/sagas/common/anonymous.js | 28 ++++++------- packages/common/sagas/entities/item.js | 11 +++-- packages/common/sagas/entities/member.js | 3 ++ packages/common/sagas/entities/user.js | 43 +++++++++++++++++++- packages/common/utils/item.js | 5 ++- packages/components/ShareModal/ShareModal.js | 2 +- 11 files changed, 115 insertions(+), 30 deletions(-) diff --git a/packages/common/actions/entities/item.js b/packages/common/actions/entities/item.js index 8d70db60f..af64b268b 100644 --- a/packages/common/actions/entities/item.js +++ b/packages/common/actions/entities/item.js @@ -311,11 +311,11 @@ export const createAnonymousLinkRequest = () => ({ type: CREATE_ANONYMOUS_LINK_REQUEST, }); -export const createAnonymousLinkSuccess = (itemId, share) => ({ +export const createAnonymousLinkSuccess = (itemId, shared) => ({ type: CREATE_ANONYMOUS_LINK_SUCCESS, payload: { itemId, - share, + shared, }, }); diff --git a/packages/common/actions/entities/member.js b/packages/common/actions/entities/member.js index 1584fa5c5..cff30b26a 100644 --- a/packages/common/actions/entities/member.js +++ b/packages/common/actions/entities/member.js @@ -58,11 +58,12 @@ export const fetchTeamMembersFailure = () => ({ type: FETCH_TEAM_MEMBERS_FAILURE, }); -export const createMemberRequest = (email, role) => ({ +// @Deprecated +export const createMemberRequest = (email, teamRole) => ({ type: CREATE_MEMBER_REQUEST, payload: { email, - role, + teamRole, }, }); @@ -77,11 +78,11 @@ export const createMemberFailure = () => ({ type: CREATE_MEMBER_FAILURE, }); -export const createMemberBatchRequest = (email, role) => ({ +export const createMemberBatchRequest = (email, teamRole) => ({ type: CREATE_MEMBER_BATCH_REQUEST, payload: { email, - role, + teamRole, }, }); diff --git a/packages/common/actions/entities/user.js b/packages/common/actions/entities/user.js index d1ab883b5..e94c3b99f 100644 --- a/packages/common/actions/entities/user.js +++ b/packages/common/actions/entities/user.js @@ -2,6 +2,10 @@ export const FETCH_USERS_REQUEST = '@user/FETCH_USERS_REQUEST'; export const FETCH_USERS_SUCCESS = '@user/FETCH_USERS_SUCCESS'; export const FETCH_USERS_FAILURE = '@user/FETCH_USERS_FAILURE'; +export const CREATE_USER_REQUEST = '@user/CREATE_USER_REQUEST'; +export const CREATE_USER_SUCCESS = '@user/CREATE_USER_SUCCESS'; +export const CREATE_USER_FAILURE = '@user/CREATE_USER_FAILURE'; + export const RESET_USER_STATE = '@user/RESET_USER_STATE'; export const fetchUsersRequest = () => ({ @@ -19,6 +23,22 @@ export const fetchUsersFailure = () => ({ type: FETCH_USERS_FAILURE, }); +export const createUserRequest = payload => ({ + type: CREATE_USER_REQUEST, + payload, +}); + +export const createUserSuccess = user => ({ + type: CREATE_USER_SUCCESS, + payload: { + user, + }, +}); + +export const createUserFailure = () => ({ + type: CREATE_USER_FAILURE, +}); + export const resetUserState = () => ({ type: RESET_USER_STATE, }); diff --git a/packages/common/reducers/entities/item.js b/packages/common/reducers/entities/item.js index 6f14e3cd7..e6a21786e 100644 --- a/packages/common/reducers/entities/item.js +++ b/packages/common/reducers/entities/item.js @@ -240,7 +240,7 @@ export default createReducer(initialState, { ...state.byId, [payload.itemId]: { ...state.byId[payload.itemId], - shared: payload.share, + shared: payload.shared, }, }, }; diff --git a/packages/common/reducers/entities/user.js b/packages/common/reducers/entities/user.js index fbace84e8..b9387b51d 100644 --- a/packages/common/reducers/entities/user.js +++ b/packages/common/reducers/entities/user.js @@ -3,6 +3,9 @@ import { FETCH_USERS_REQUEST, FETCH_USERS_SUCCESS, FETCH_USERS_FAILURE, + CREATE_USER_REQUEST, + CREATE_USER_SUCCESS, + CREATE_USER_FAILURE, RESET_USER_STATE, } from '@caesar/common/actions/entities/user'; @@ -27,6 +30,21 @@ export default createReducer(initialState, { [FETCH_USERS_FAILURE](state) { return { ...state, isLoading: false, isError: true }; }, + [CREATE_USER_REQUEST](state) { + return state; + }, + [CREATE_USER_SUCCESS](state, { payload }) { + return { + ...state, + byId: { + ...state.byId, + [payload.user.id]: payload.user, + }, + }; + }, + [CREATE_USER_FAILURE](state) { + return state; + }, [RESET_USER_STATE]() { return initialState; }, diff --git a/packages/common/sagas/common/anonymous.js b/packages/common/sagas/common/anonymous.js index e869f9ed6..7bcd4fdbe 100644 --- a/packages/common/sagas/common/anonymous.js +++ b/packages/common/sagas/common/anonymous.js @@ -1,10 +1,8 @@ import { put, call, takeLatest, select } from 'redux-saga/effects'; import { workInProgressItemSelector } from '@caesar/common/selectors/workflow'; -import { - encryptItem, - generateAnonymousEmail, -} from '@caesar/common/utils/cipherUtils'; -import { createMemberSaga } from '@caesar/common/sagas/entities/member'; +import { encryptItem } from '@caesar/common/utils/cipherUtils'; +import { generateAnonymousEmail } from '@caesar/common/utils/item'; +import { createUserSaga } from '@caesar/common/sagas/entities/user'; import { DOMAIN_ROLES } from '@caesar/common/constants'; import { generateSharingUrl } from '@caesar/common/utils/sharing'; import { objectToBase64 } from '@caesar/common/utils/base64'; @@ -24,7 +22,7 @@ export function* createAnonymousLinkSaga() { try { const workInProgressItem = yield select(workInProgressItemSelector); - const email = generateAnonymousEmail(); + const email = generateAnonymousEmail(workInProgressItem.id); const { id: userId, @@ -32,15 +30,14 @@ export function* createAnonymousLinkSaga() { password, masterPassword, publicKey, - } = yield call(createMemberSaga, { + domainRoles, + } = yield call(createUserSaga, { payload: { email, - role: DOMAIN_ROLES.ROLE_ANONYMOUS_USER, + domainRole: DOMAIN_ROLES.ROLE_ANONYMOUS_USER, }, }); - // TODO: Add new flow of sharing with keipair.share - console.log('Anonym flow will be implemented soon'); const encryptedSecret = yield call( encryptItem, workInProgressItem.data, @@ -48,7 +45,7 @@ export function* createAnonymousLinkSaga() { ); const item = { - id: 'itemId', + id: workInProgressItem.id, secret: encryptedSecret, }; @@ -58,11 +55,11 @@ export function* createAnonymousLinkSaga() { objectToBase64({ e: email, p: password, - mp: masterPassword, + m: masterPassword, }), ); - const share = { + const shared = { id: item.id, userId, email, @@ -70,10 +67,11 @@ export function* createAnonymousLinkSaga() { link, publicKey, isAccepted: false, - domainRoles: [DOMAIN_ROLES.ROLE_ANONYMOUS_USER], + domainRoles, }; - yield put(createAnonymousLinkSuccess(workInProgressItem.id, share)); + // TODO: Add request to update 'shared' item + yield put(createAnonymousLinkSuccess(workInProgressItem.id, shared)); yield put(updateWorkInProgressItem()); } catch (error) { // eslint-disable-next-line no-console diff --git a/packages/common/sagas/entities/item.js b/packages/common/sagas/entities/item.js index 5f1695ce8..fa3272ac7 100644 --- a/packages/common/sagas/entities/item.js +++ b/packages/common/sagas/entities/item.js @@ -768,7 +768,7 @@ export function* updateItemSaga({ payload: { item } }) { export function* editItemSaga({ payload: { item }, - meta: { setSubmitting, notification }, + meta: { setSubmitting = Function.prototype, notification } = {}, }) { try { const { @@ -804,9 +804,12 @@ export function* editItemSaga({ } yield put(updateWorkInProgressItem(item.id)); - yield call(notification.show, { - text: `The '${item.data.name}' has been updated`, - }); + + if (notification) { + yield call(notification.show, { + text: `The '${item.data.name}' has been updated`, + }); + } } catch (error) { // eslint-disable-next-line no-console console.error(error); diff --git a/packages/common/sagas/entities/member.js b/packages/common/sagas/entities/member.js index c4a3be5d8..30a44954e 100644 --- a/packages/common/sagas/entities/member.js +++ b/packages/common/sagas/entities/member.js @@ -60,6 +60,7 @@ const setNewFlag = (members, isNew) => const renameUserId = members => members.map(({ userId, ...member }) => ({ id: userId, ...member })); +// TODO: replace with create user export function* createMemberSaga({ payload: { email, role } }) { try { const { password, masterPassword, publicKey, privateKey } = yield call( @@ -102,6 +103,7 @@ export function* createMemberSaga({ payload: { email, role } }) { } } +// TODO: replace with create user batch export function* createMemberBatchSaga({ payload: { emailRolePairs } }) { try { if (!emailRolePairs.length) { @@ -176,6 +178,7 @@ export function* createMemberBatchSaga({ payload: { emailRolePairs } }) { } } +// TODO: For what stuff do we need it? export function* getOrCreateMemberBatchSaga({ payload: { emailRolePairs } }) { try { const emailRoleObject = emailRolePairs.reduce( diff --git a/packages/common/sagas/entities/user.js b/packages/common/sagas/entities/user.js index c6ef1e794..a37147ff2 100644 --- a/packages/common/sagas/entities/user.js +++ b/packages/common/sagas/entities/user.js @@ -3,9 +3,18 @@ import { FETCH_USERS_REQUEST, fetchUsersSuccess, fetchUsersFailure, + CREATE_USER_REQUEST, + createUserSuccess, + createUserFailure, } from '@caesar/common/actions/entities/user'; -import { getUsers } from '@caesar/common/api'; +import { getUsers, postNewUser } from '@caesar/common/api'; import { convertUsersToEntity } from '@caesar/common/normalizers/normalizers'; +import { + generateSeedAndVerifier, + generateUser, + generateUsersBatch, +} from '@caesar/common/utils/cipherUtils'; +import { ENTITY_TYPE, DOMAIN_ROLES } from '@caesar/common/constants'; export function* fetchUsersSaga() { try { @@ -19,6 +28,38 @@ export function* fetchUsersSaga() { } } +export function* createUserSaga({ payload: { email, domainRole } }) { + try { + const { password, publicKey, privateKey } = yield call(generateUser, email); + + const { seed, verifier } = generateSeedAndVerifier(email, password); + + const data = { + email, + plainPassword: password, + publicKey, + encryptedPrivateKey: privateKey, + seed, + verifier, + domainRoles: [domainRole], + }; + + const { data: user } = yield call(postNewUser, data); + const convertedUser = convertUsersToEntity([{ ...data, ...user }]); + + if (domainRole !== DOMAIN_ROLES.ROLE_ANONYMOUS_USER) { + yield put(createUserSuccess(convertedUser)); + } + + return convertedUser[user.id]; + } catch (error) { + yield put(createUserFailure()); + + return null; + } +} + export default function* userSagas() { yield takeLatest(FETCH_USERS_REQUEST, fetchUsersSaga); + yield takeLatest(CREATE_USER_REQUEST, createUserSaga); } diff --git a/packages/common/utils/item.js b/packages/common/utils/item.js index 93b2374a8..f5a20898e 100644 --- a/packages/common/utils/item.js +++ b/packages/common/utils/item.js @@ -1,7 +1,8 @@ import { getHostName } from '@caesar/common/utils/getDomainName'; +import { ITEM_TYPE, DOMAIN_HOSTNAME } from '../constants'; import { processUploadedFiles } from './attachment'; import { decryptItem } from './cipherUtils'; -import { ITEM_TYPE, DOMAIN_HOSTNAME } from '../constants'; +import { randomId } from './uuid4'; export const extractItemType = item => item?.type || ITEM_TYPE.SYSTEM; @@ -56,7 +57,7 @@ export const generateSystemItemEmail = entityName => { }; export const generateAnonymousEmail = itemId => - `anonymous+${itemId}@${DOMAIN_HOSTNAME || getHostName()}`; + `anonymous+${itemId}-${randomId()}@${DOMAIN_HOSTNAME || getHostName()}`; export const extractKeysFromSystemItem = item => { const itemRaws = item.data?.raws || { diff --git a/packages/components/ShareModal/ShareModal.js b/packages/components/ShareModal/ShareModal.js index 0c609698a..b82cb8d26 100644 --- a/packages/components/ShareModal/ShareModal.js +++ b/packages/components/ShareModal/ShareModal.js @@ -75,7 +75,7 @@ export const ShareModal = ({ sharedMembers, items, teams, - anonymousLink = [], + anonymousLink = {}, isMultiMode = false, onActivateLink = Function.prototype, onDeactivateLink = Function.prototype, From fe5a5eeb66c9035eb808c4c1d1aca862e1b820f8 Mon Sep 17 00:00:00 2001 From: cu Date: Mon, 2 Nov 2020 16:54:45 +0300 Subject: [PATCH 3/6] [WIP] CAES-1443 --- packages/common/sagas/common/anonymous.js | 31 ++++++++++------------- packages/common/sagas/entities/user.js | 4 ++- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/common/sagas/common/anonymous.js b/packages/common/sagas/common/anonymous.js index 7bcd4fdbe..10e86d73e 100644 --- a/packages/common/sagas/common/anonymous.js +++ b/packages/common/sagas/common/anonymous.js @@ -1,6 +1,5 @@ import { put, call, takeLatest, select } from 'redux-saga/effects'; import { workInProgressItemSelector } from '@caesar/common/selectors/workflow'; -import { encryptItem } from '@caesar/common/utils/cipherUtils'; import { generateAnonymousEmail } from '@caesar/common/utils/item'; import { createUserSaga } from '@caesar/common/sagas/entities/user'; import { DOMAIN_ROLES } from '@caesar/common/constants'; @@ -17,6 +16,7 @@ import { import { updateWorkInProgressItem } from '@caesar/common/actions/workflow'; import { updateGlobalNotification } from '@caesar/common/actions/application'; import { getServerErrorMessage } from '@caesar/common/utils/error'; +import { shareItemBatchSaga } from './share'; export function* createAnonymousLinkSaga() { try { @@ -24,6 +24,13 @@ export function* createAnonymousLinkSaga() { const email = generateAnonymousEmail(workInProgressItem.id); + const createdUser = yield call(createUserSaga, { + payload: { + email, + domainRole: DOMAIN_ROLES.ROLE_ANONYMOUS_USER, + }, + }); + const { id: userId, name, @@ -31,27 +38,17 @@ export function* createAnonymousLinkSaga() { masterPassword, publicKey, domainRoles, - } = yield call(createUserSaga, { + } = createdUser; + + yield call(shareItemBatchSaga, { payload: { - email, - domainRole: DOMAIN_ROLES.ROLE_ANONYMOUS_USER, + data: { itemIds: [workInProgressItem.id], members: [createdUser] }, }, }); - const encryptedSecret = yield call( - encryptItem, - workInProgressItem.data, - publicKey, - ); - - const item = { - id: workInProgressItem.id, - secret: encryptedSecret, - }; - // TODO: Make shorted link const link = generateSharingUrl( - item.id, + workInProgressItem.id, objectToBase64({ e: email, p: password, @@ -60,7 +57,7 @@ export function* createAnonymousLinkSaga() { ); const shared = { - id: item.id, + id: workInProgressItem.id, userId, email, name, diff --git a/packages/common/sagas/entities/user.js b/packages/common/sagas/entities/user.js index a37147ff2..e62f03f94 100644 --- a/packages/common/sagas/entities/user.js +++ b/packages/common/sagas/entities/user.js @@ -41,7 +41,9 @@ export function* createUserSaga({ payload: { email, domainRole } }) { encryptedPrivateKey: privateKey, seed, verifier, - domainRoles: [domainRole], + // TODO: replace with domainRoles when @aburov merges his pr + roles: [domainRole], + // domainRoles: [domainRole], }; const { data: user } = yield call(postNewUser, data); From efd74b2fd8f49f488542f1bff76adc8b52498d92 Mon Sep 17 00:00:00 2001 From: cu Date: Tue, 10 Nov 2020 12:50:30 +0300 Subject: [PATCH 4/6] [Anonym] CAES-1443: WIP --- packages/common/sagas/entities/user.js | 4 +--- packages/common/selectors/workflow.js | 6 +++++- packages/components/InviteModal/InviteModal.js | 4 ++-- packages/components/Item/components/OwnerAndShares.js | 1 + packages/components/ShareModal/ShareModal.js | 3 +-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/common/sagas/entities/user.js b/packages/common/sagas/entities/user.js index e62f03f94..a37147ff2 100644 --- a/packages/common/sagas/entities/user.js +++ b/packages/common/sagas/entities/user.js @@ -41,9 +41,7 @@ export function* createUserSaga({ payload: { email, domainRole } }) { encryptedPrivateKey: privateKey, seed, verifier, - // TODO: replace with domainRoles when @aburov merges his pr - roles: [domainRole], - // domainRoles: [domainRole], + domainRoles: [domainRole], }; const { data: user } = yield call(postNewUser, data); diff --git a/packages/common/selectors/workflow.js b/packages/common/selectors/workflow.js index 6af53cf71..0a5c96a2f 100644 --- a/packages/common/selectors/workflow.js +++ b/packages/common/selectors/workflow.js @@ -94,7 +94,11 @@ export const workInProgressItemSharedMembersSelector = createSelector( workInProgressItemSelector, usersByIdSelector, (workInProgressItem, usersById) => - workInProgressItem?.invited?.map(userId => usersById[userId]) || [], + workInProgressItem?.invited?.reduce((acc, userId) => { + const user = usersById[userId]; + + return user ? [...acc, user] : acc; + }, []) || [], ); const createListItemsList = (children, itemsById) => diff --git a/packages/components/InviteModal/InviteModal.js b/packages/components/InviteModal/InviteModal.js index 5dec1ada5..54778814b 100755 --- a/packages/components/InviteModal/InviteModal.js +++ b/packages/components/InviteModal/InviteModal.js @@ -50,11 +50,11 @@ class InviteModal extends Component { })); }; - handleChangeRole = changedRoleMember => (_, role) => { + handleChangeRole = updatedMember => (_, teamRole) => { this.setState(prevState => ({ ...prevState, users: prevState.users.map(member => - member.id === changedRoleMember.id ? { ...member, role } : member, + member.id === updatedMember.id ? { ...member, role: teamRole } : member, ), })); }; diff --git a/packages/components/Item/components/OwnerAndShares.js b/packages/components/Item/components/OwnerAndShares.js index b33622221..2c4a9a3f6 100755 --- a/packages/components/Item/components/OwnerAndShares.js +++ b/packages/components/Item/components/OwnerAndShares.js @@ -55,6 +55,7 @@ const NoMembers = styled.div` `; const ShareButton = styled.button` + flex: 0 0 40px; width: 40px; height: 40px; padding: 0; diff --git a/packages/components/ShareModal/ShareModal.js b/packages/components/ShareModal/ShareModal.js index b1e8a9215..b82cb8d26 100644 --- a/packages/components/ShareModal/ShareModal.js +++ b/packages/components/ShareModal/ShareModal.js @@ -84,7 +84,6 @@ export const ShareModal = ({ onRevokeAccess = Function.prototype, onRemove = Function.prototype, }) => { - const disableAnonymLink = true; const [members, setMembers] = useState([]); const [teamIds, setTeamIds] = useState([]); const [isOpenedInvited, setOpenedInvited] = useState(false); @@ -205,7 +204,7 @@ export const ShareModal = ({ )} - {!disableAnonymLink && !isMultiMode && ( + {!isMultiMode && ( Date: Tue, 10 Nov 2020 18:14:49 +0300 Subject: [PATCH 5/6] [Anonym] CAES-1443: WIP --- packages/common/sagas/common/anonymous.js | 2 +- packages/common/sagas/entities/user.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/common/sagas/common/anonymous.js b/packages/common/sagas/common/anonymous.js index 10e86d73e..3f4c79ac0 100644 --- a/packages/common/sagas/common/anonymous.js +++ b/packages/common/sagas/common/anonymous.js @@ -34,7 +34,7 @@ export function* createAnonymousLinkSaga() { const { id: userId, name, - password, + plainPassword: password, masterPassword, publicKey, domainRoles, diff --git a/packages/common/sagas/entities/user.js b/packages/common/sagas/entities/user.js index a37147ff2..afbb4ba6b 100644 --- a/packages/common/sagas/entities/user.js +++ b/packages/common/sagas/entities/user.js @@ -30,7 +30,10 @@ export function* fetchUsersSaga() { export function* createUserSaga({ payload: { email, domainRole } }) { try { - const { password, publicKey, privateKey } = yield call(generateUser, email); + const { password, masterPassword, publicKey, privateKey } = yield call( + generateUser, + email, + ); const { seed, verifier } = generateSeedAndVerifier(email, password); @@ -45,12 +48,14 @@ export function* createUserSaga({ payload: { email, domainRole } }) { }; const { data: user } = yield call(postNewUser, data); - const convertedUser = convertUsersToEntity([{ ...data, ...user }]); - if (domainRole !== DOMAIN_ROLES.ROLE_ANONYMOUS_USER) { - yield put(createUserSuccess(convertedUser)); + if (domainRole === DOMAIN_ROLES.ROLE_ANONYMOUS_USER) { + return { ...data, ...user, masterPassword }; } + const convertedUser = convertUsersToEntity([{ ...data, ...user }]); + yield put(createUserSuccess(convertedUser)); + return convertedUser[user.id]; } catch (error) { yield put(createUserFailure()); From aff9d3d46835d07578a43f778f69eaebf57dbb58 Mon Sep 17 00:00:00 2001 From: cu Date: Thu, 12 Nov 2020 19:06:49 +0300 Subject: [PATCH 6/6] [Anonym] CAES-1443: WIP --- packages/common/actions/workflow.js | 5 ++ packages/common/api.js | 1 - packages/common/sagas/common/anonymous.js | 23 ++----- packages/common/sagas/workflow.js | 15 ++++- packages/common/utils/sharing.js | 4 +- packages/components/ShareModal/utils.js | 2 +- packages/containers/Sharing/Sharing.js | 79 +++++++---------------- packages/containers/Sharing/index.js | 2 +- packages/web-app/pages/share.js | 8 +-- packages/web-app/server.js | 3 +- 10 files changed, 51 insertions(+), 91 deletions(-) diff --git a/packages/common/actions/workflow.js b/packages/common/actions/workflow.js index 1d5487da4..b13ad23bd 100644 --- a/packages/common/actions/workflow.js +++ b/packages/common/actions/workflow.js @@ -6,6 +6,7 @@ export const INIT_TEAM_SETTINGS = '@workflow/INIT_TEAM_SETTINGS'; export const INIT_IMPORT_SETTINGS = '@workflow/INIT_IMPORT_SETTINGS'; export const INIT_CREATE_PAGE = '@workflow/INIT_CREATE_PAGE'; export const INIT_DASHBOARD = '@workflow/INIT_DASHBOARD'; +export const INIT_SHARE = '@workflow/INIT_SHARE'; export const FINISH_PROCESSING_KEYPAIRS = '@workflow/FINISH_PROCESSING_KEYPAIRS'; @@ -69,6 +70,10 @@ export const initDashboard = () => ({ type: INIT_DASHBOARD, }); +export const initShare = () => ({ + type: INIT_SHARE, +}); + export const finishProcessingKeyPairs = () => ({ type: FINISH_PROCESSING_KEYPAIRS, }); diff --git a/packages/common/api.js b/packages/common/api.js index 38b2d4169..78bf49412 100755 --- a/packages/common/api.js +++ b/packages/common/api.js @@ -209,7 +209,6 @@ export const getLastUpdatedUserItems = ( export const postItemShare = ({ itemId, users }) => callApi.post(`/items/${itemId}/share`, { users }); -export const getCheckShare = id => callApi.get(`/anonymous/share/${id}/check`); // item batch export const postCreateItemsBatch = data => callApi.post('/items/batch', data); diff --git a/packages/common/sagas/common/anonymous.js b/packages/common/sagas/common/anonymous.js index 3f4c79ac0..c81e33be5 100644 --- a/packages/common/sagas/common/anonymous.js +++ b/packages/common/sagas/common/anonymous.js @@ -31,14 +31,7 @@ export function* createAnonymousLinkSaga() { }, }); - const { - id: userId, - name, - plainPassword: password, - masterPassword, - publicKey, - domainRoles, - } = createdUser; + const { plainPassword: password, masterPassword } = createdUser; yield call(shareItemBatchSaga, { payload: { @@ -48,7 +41,6 @@ export function* createAnonymousLinkSaga() { // TODO: Make shorted link const link = generateSharingUrl( - workInProgressItem.id, objectToBase64({ e: email, p: password, @@ -56,16 +48,9 @@ export function* createAnonymousLinkSaga() { }), ); - const shared = { - id: workInProgressItem.id, - userId, - email, - name, - link, - publicKey, - isAccepted: false, - domainRoles, - }; + // TODO: Rename here and on backend to 'anonymLink' or move to secret + // TODO: Encrypt with user key => maybe in secret + const shared = link; // TODO: Add request to update 'shared' item yield put(createAnonymousLinkSuccess(workInProgressItem.id, shared)); diff --git a/packages/common/sagas/workflow.js b/packages/common/sagas/workflow.js index a38af38d6..06daf2dd1 100644 --- a/packages/common/sagas/workflow.js +++ b/packages/common/sagas/workflow.js @@ -14,6 +14,7 @@ import { INIT_TEAMS_SETTINGS, INIT_TEAM_SETTINGS, INIT_IMPORT_SETTINGS, + INIT_SHARE, UPDATE_WORK_IN_PROGRESS_ITEM, SET_WORK_IN_PROGRESS_ITEM, finishIsLoading, @@ -655,7 +656,9 @@ function* getItemKeyPair({ function* decryptItemRaws({ payload: { item } }) { try { // If item is null or aready had decypted attachments then do not dectrypt again! - if (!item || (item?.data?.raws && Object.values(item?.data?.raws) > 0)) return; + if (!item || (item?.data?.raws && Object.values(item?.data?.raws) > 0)) { + return; + } const { raws } = JSON.parse(item.secret); @@ -762,6 +765,15 @@ function* initImportSettingsSaga() { } } +function* initShareSaga() { + try { + console.log('Implement init share flow'); + } catch (error) { + // eslint-disable-next-line no-console + console.error('error: ', error); + } +} + function* vaultsReadySaga() { yield call(initTeamsSaga); yield put(vaultsReady()); @@ -790,6 +802,7 @@ export default function* workflowSagas() { yield takeLatest(INIT_TEAMS_SETTINGS, initTeamsSettingsSaga); yield takeLatest(INIT_TEAM_SETTINGS, initTeamSettingsSaga); yield takeLatest(INIT_IMPORT_SETTINGS, initImportSettingsSaga); + yield takeLatest(INIT_SHARE, initShareSaga); yield takeLatest(SET_WORK_IN_PROGRESS_ITEM, setWorkInProgressItemSaga); yield takeLatest(UPDATE_WORK_IN_PROGRESS_ITEM, updateWorkInProgressItemSaga); diff --git a/packages/common/utils/sharing.js b/packages/common/utils/sharing.js index 1fe12f8f5..57292ef4b 100644 --- a/packages/common/utils/sharing.js +++ b/packages/common/utils/sharing.js @@ -1,7 +1,7 @@ import { APP_URI } from '@caesar/common/constants'; -export const generateSharingUrl = (shareId, encryption) => - `${APP_URI}/share/${shareId}/${encryption}`; +export const generateSharingUrl = encryption => + `${APP_URI}/share/${encryption}`; export const generateInviteUrl = encryption => `${APP_URI}/invite/${encryption}`; diff --git a/packages/components/ShareModal/utils.js b/packages/components/ShareModal/utils.js index f182a5a3d..99beed520 100644 --- a/packages/components/ShareModal/utils.js +++ b/packages/components/ShareModal/utils.js @@ -1,6 +1,6 @@ import { APP_URI } from '@caesar/common/constants'; -export const getAnonymousLink = shared => (shared && shared.link) || null; +export const getAnonymousLink = shared => shared || null; export const hideLink = link => `${APP_URI}/${link diff --git a/packages/containers/Sharing/Sharing.js b/packages/containers/Sharing/Sharing.js index 7082b27d5..4cf63a8fc 100644 --- a/packages/containers/Sharing/Sharing.js +++ b/packages/containers/Sharing/Sharing.js @@ -1,66 +1,31 @@ -import React, { Component, memo } from 'react'; -import { getLists } from '@caesar/common/api'; +import React, { memo } from 'react'; +import { useEffectOnce } from 'react-use'; +import { useDispatch } from 'react-redux'; import { SharingLayout } from '@caesar/components'; import { ItemByType } from '@caesar/components/Item/ItemByType'; -import { LIST_TYPE, PERMISSION_ENTITY } from '@caesar/common/constants'; -import { - unsealPrivateKeyObj, - decryptItem, -} from '@caesar/common/utils/cipherUtils'; +import { initShare } from '@caesar/common/actions/workflow'; -const getInboxItem = list => { - const inbox = list.find(({ type }) => type === LIST_TYPE.INBOX); +const SharingComponent = () => { + const item = null; + const dispatch = useDispatch(); - if (!inbox || !inbox.children) { + useEffectOnce(() => { + dispatch(initShare()); + }); + + if (!item) { return null; } - return inbox.children[0]; + return ( + + + + ); }; -class SharingComponent extends Component { - state = this.prepareInitialState(); - - async componentDidMount() { - const { privateKey, password } = this.props; - - const { data: list } = await getLists(); - - const item = getInboxItem(list); - - const privateKeyObj = await unsealPrivateKeyObj(privateKey, password); - const decryptedSecret = await decryptItem(item.secret, privateKeyObj); - - this.setState({ - item: { ...item, data: decryptedSecret }, - }); - } - - prepareInitialState() { - return { - item: null, - }; - } - - render() { - const { item } = this.state; - - if (!item) { - return null; - } - - const itemSubject = { - __typename: PERMISSION_ENTITY.ITEM, - }; - - return ( - - - - ); - } -} - -const Sharing = memo(SharingComponent); - -export default Sharing; +export const Sharing = memo(SharingComponent); diff --git a/packages/containers/Sharing/index.js b/packages/containers/Sharing/index.js index ee5798316..b1498b4e0 100644 --- a/packages/containers/Sharing/index.js +++ b/packages/containers/Sharing/index.js @@ -1 +1 @@ -export { default as Sharing } from './Sharing'; +export { Sharing } from './Sharing'; diff --git a/packages/web-app/pages/share.js b/packages/web-app/pages/share.js index ec0d63c47..9a3ddf4b2 100644 --- a/packages/web-app/pages/share.js +++ b/packages/web-app/pages/share.js @@ -3,7 +3,6 @@ import { Error, Head } from '@caesar/components'; import { Bootstrap, Sharing } from '@caesar/containers'; import { base64ToObject } from '@caesar/common/utils/base64'; import { login } from '@caesar/common/utils/authUtils'; -import { getCheckShare } from '@caesar/common/api'; const validFields = ['e', 'p']; @@ -21,10 +20,7 @@ const SharePage = ({ statusCode, shared }) => ( ); -SharePage.getInitialProps = async ({ - res, - query: { encryption = '', shareId = '' }, -}) => { +SharePage.getInitialProps = async ({ res, query: { encryption = '' } }) => { const shared = base64ToObject(encryption); const isFieldsValidated = validateFields(shared, validFields); @@ -46,8 +42,6 @@ SharePage.getInitialProps = async ({ } try { - await getCheckShare(shareId); - const jwt = await login(shared.e, shared.p); res.cookie('token', jwt, { path: '/' }); diff --git a/packages/web-app/server.js b/packages/web-app/server.js index 904fe8a81..2d92a8ee9 100755 --- a/packages/web-app/server.js +++ b/packages/web-app/server.js @@ -77,9 +77,8 @@ app.prepare().then(() => { }); }); - server.get('/share/:shareId/:encryption', (req, res) => { + server.get('/share/:encryption', (req, res) => { app.render(req, res, '/share', { - shareId: req.params.shareId, encryption: req.params.encryption, }); });