diff --git a/src/components/user/User.tsx b/src/components/user/User.tsx index 4a92a8f..9796d55 100644 --- a/src/components/user/User.tsx +++ b/src/components/user/User.tsx @@ -19,6 +19,8 @@ import { isFunction } from 'lodash'; import { userId } from '~/lib/user-id'; import { userExists } from '~/lib/user-exists'; import { update } from '~/lib/update'; +import { replicationDocument } from '~/lib/replication-document'; +import { get } from '~/lib/get'; interface Props { values?: () => any; @@ -114,6 +116,10 @@ const User: Component = (props) => { database, }; + const currentUserId = userId(data('username'), data('database')); + const credentials = adminCredentials(); + const adminUser = credentials?.username || ''; + for (let i = 0; i < updatedSubscriptions.length; i++) { let subscription = updatedSubscriptions[i]; if (!isIn(subscription.username, previousSubscriptions())) { @@ -122,6 +128,24 @@ const User: Component = (props) => { addSubscriptionFactory(subscription.username), defaultUserDocument ); + const otherUserId = userId(subscription.username, data('database')); + const otherUserPassword = (await get(otherUserId)).password; + let repDocument = replicationDocument( + currentUserId, + data('password'), + otherUserId, + otherUserPassword, + adminUser + ); + await put(repDocument._id, repDocument, false, '_replicator'); + repDocument = replicationDocument( + otherUserId, + otherUserPassword, + currentUserId, + data('password'), + adminUser + ); + await put(repDocument._id, repDocument, false, '_replicator'); console.log({ caller: 'User / submitHandler / new subscription', username: subscription.username, @@ -139,6 +163,7 @@ const User: Component = (props) => { removeSubscriptionFactory(subscription.username), defaultUserDocument ); + console.log({ caller: 'User / submitHandler / deleted subscription', username: subscription.username, @@ -269,7 +294,7 @@ const User: Component = (props) => { `${value} of ${max} tasks completed` } diff --git a/src/lib/get-db-url-for-user.ts b/src/lib/get-db-url-for-user.ts new file mode 100644 index 0000000..21d3e64 --- /dev/null +++ b/src/lib/get-db-url-for-user.ts @@ -0,0 +1,4 @@ +import { toHex } from './to-hex'; + +export const getDbUrlForUser = (user: { username: string; hostname: string }) => + `https://${user.hostname}/userdb-${toHex(user.username)}`; diff --git a/src/lib/replication-document.ts b/src/lib/replication-document.ts new file mode 100644 index 0000000..0bec43d --- /dev/null +++ b/src/lib/replication-document.ts @@ -0,0 +1,51 @@ +import { parseUserId } from './user-id'; +import { getDbUrlForUser } from './get-db-url-for-user'; + +export const replicationDocument = ( + userFromId: string, + userFromPassword: string, + userToId: string, + userToPassword: string, + adminUser: string +) => { + const userFrom = parseUserId(userFromId); + const userTo = parseUserId(userToId); + + const document = { + _id: `${userFromId}=>${userToId}`, + user_ctx: { + name: adminUser, + roles: ['_admin', '_reader', '_writer'], + }, + source: { + url: getDbUrlForUser(userFrom), + auth: { + basic: { + username: userFrom.username, + password: userFromPassword, + }, + }, + }, + target: { + url: getDbUrlForUser(userTo), + auth: { + basic: { + username: userTo.username, + password: userToPassword, + }, + }, + }, + selector: { + to: { + $elemMatch: { + $eq: userToId, + }, + }, + }, + create_target: false, + continuous: true, + owner: adminUser, + }; + + return document; +}; diff --git a/src/lib/update.ts b/src/lib/update.ts new file mode 100644 index 0000000..140ce4c --- /dev/null +++ b/src/lib/update.ts @@ -0,0 +1,24 @@ +import { get } from './get'; +import { put } from './put'; + +export const update = async ( + id: string, + updater: (doc: any) => any, + defaultDocument: any, + db = 'dyomedea_users' +) => { + const previous = await get(id, db); + + const newDocument = updater(previous || defaultDocument); + + console.log({ + caller: 'update', + id, + defaultDocument, + db, + previous, + newDocument, + }); + + return await put(id, newDocument, false, db); +}; diff --git a/src/lib/user-exists.ts b/src/lib/user-exists.ts new file mode 100644 index 0000000..8bc948a --- /dev/null +++ b/src/lib/user-exists.ts @@ -0,0 +1,30 @@ +import { adminCredentials } from '~/components/credentials'; +import { headersWithAuth } from './headers-with-auth'; + +export const userExists = async (username: string) => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + + const headers = headersWithAuth(); + if (!headers) { + return null; + } + + try { + const response = await fetch( + `${database}/_users/org.couchdb.user:${username}`, + { + method: 'HEAD', + mode: 'cors', + headers, + } + ); + return response.status === 200; + } catch (error) { + console.error({ caller: 'userExists', error }); + return null; + } +};