Skip to content

Commit

Permalink
feat(csam): add toggle for csam scan and save info to textile (#2809)
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Lee committed Apr 13, 2022
1 parent ab3167f commit 3dec325
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 1 deletion.
9 changes: 8 additions & 1 deletion components/views/settings/pages/privacy/Privacy.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@
<InteractablesSwitch v-model="displayCurrentActivity" />
</SettingsUnit>
</div>

<div class="columns is-desktop">
<SettingsUnit
:title="$t('pages.privacy.consentScan.title')"
:text="$t('pages.privacy.consentScan.subtitle')"
>
<InteractablesSwitch v-model="consentScan" />
</SettingsUnit>
</div>
<div class="columns is-desktop">
<SettingsUnit
:title="$t('pages.privacy.serverType.title')"
Expand Down
8 changes: 8 additions & 0 deletions components/views/settings/pages/privacy/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ export default Vue.extend({
return this.settings.embeddedLinks
},
},
consentScan: {
set(state) {
this.$store.dispatch('settings/setConsentScan', state)
},
get() {
return this.settings.consentScan
},
},
displayCurrentActivity: {
set(state) {
this.$store.commit('settings/displayCurrentActivity', state)
Expand Down
6 changes: 6 additions & 0 deletions libraries/Textile/TextileManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Bucket } from '../Files/remote/textile/Bucket'
import { MetadataManager } from './MetadataManager'
import { UserInfoManager } from './UserManager'
import IdentityManager from '~/libraries/Textile/IdentityManager'
import { MailboxManager } from '~/libraries/Textile/MailboxManager'
import {
Expand All @@ -19,6 +20,7 @@ export default class TextileManager {
bucket?: Bucket
groupChatManager?: GroupChatManager
metadataManager?: MetadataManager
userInfoManager?: UserInfoManager

constructor() {
this.identityManager = new IdentityManager()
Expand Down Expand Up @@ -89,6 +91,10 @@ export default class TextileManager {
// MetadataManager initializes itself during the creation
this.metadataManager = new MetadataManager(textile)
await this.metadataManager.init()

// UserInfoManager initializes itself during the creation
this.userInfoManager = new UserInfoManager(textile)
await this.userInfoManager.init()
}

/**
Expand Down
116 changes: 116 additions & 0 deletions libraries/Textile/UserManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Query, ThreadID } from '@textile/hub'
import { userinfoSchema } from './schema'
import { TextileInitializationData } from '~/types/textile/manager'
import Crypto from '~/libraries/Crypto/Crypto'
import { UserdataFromThread } from '~/types/textile/user'

const CollectionName = 'userInfo'

export class UserInfoManager {
textile: TextileInitializationData
private _threadID?: ThreadID

constructor(textile: TextileInitializationData) {
this.textile = textile
}

/**
* @method init
* Initialize the userinfo manager
*/
async init() {
this._threadID = await this.getThreadID()
await this.createCollection()
}

/**
* @method threadID
* Get the thread id of the user data for current user
* @returns returns the thread id of the user data for the current user
*/
get threadID(): ThreadID {
if (!this._threadID) {
throw new Error('User info manager is not initialized.')
}
return this._threadID
}

/**
* @method createCollection
* Create a collection for managing users
*/
async createCollection(): Promise<void> {
try {
await this.textile.client.newCollection(this.threadID, {
name: CollectionName,
schema: userinfoSchema,
})
} catch (error) {}
}

/**
* Find the current user consent info
* @returns returns the current user's info
*/
private async _findRecord(): Promise<UserdataFromThread | null> {
const query = Query.where('user_address').eq(this.textile.wallet.address)
const [record] = await this.textile.client.find<UserdataFromThread>(
this.threadID,
CollectionName,
query,
)
return record || null
}

async getConsentData(): Promise<UserdataFromThread | null> {
return await this._findRecord()
}

/**
* @method setConsent
* Set consent data for csam
*/
async setConsent({
consentScan,
consentDate,
}: {
consentScan: boolean
consentDate: number
}): Promise<void> {
const record = await this._findRecord()
if (record) {
record.consent_scan = consentScan
record.consent_date = consentDate
await this.textile.client.save(this.threadID, CollectionName, [record])
return
}
await this.textile.client.create(this.threadID, CollectionName, [
{
user_address: this.textile.wallet.address,
created_at: Date.now(),
consent_scan: consentScan,
consent_date: consentDate,
},
])
}

async getThreadName(): Promise<string> {
const crypto = new Crypto()
const name = crypto.signMessageWithKey(
this.textile.wallet.keypair.secretKey,
`csam`,
)

return crypto.hash(name)
}

async getThreadID(): Promise<ThreadID> {
const name = await this.getThreadName()
try {
const thread = await this.textile.client.getThread(name)
return ThreadID.fromString(thread.id)
} catch (e) {
return await this.textile.client.newDB(undefined, name)
}
}
}
13 changes: 13 additions & 0 deletions libraries/Textile/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ export const metadataSchema = {
_mod: { type: 'number' },
},
}

export const userinfoSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'Userinfo',
type: 'object',
properties: {
_id: { type: 'string' },
created_at: { type: 'number' },
user_address: { type: 'string' },
consent_scan: { type: 'boolean' },
consent_date: { type: 'number' },
},
}
5 changes: 5 additions & 0 deletions locales/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ export default {
subtitle:
"Choose which signaling server group you want to use. If you use 'Satellite + Public Signaling Servers', you are using public servers and Satellite hosted servers to connect with your friends. We do not track connections. We only track server utilization (memory and cpu usage) to know if we need to turn on more signaling servers. If you opt to use 'Only Public Signaling Servers', those are totally outside of Satellite control, so we can not see or have any insight into their operation, logging, or data sharing practices, and you may experience difficulties connecting with friends if the signaling servers are overloaded.",
},
consentScan: {
title: 'Consents to having files scanned',
subtitle:
'In order to share files/use the encrypted file storage I consent to having my files auto-scanned against the Microsoft PhotoDNA service to help prevent the spread of sexual abuse material',
},
ownInfo: {
title: 'Set my own Signaling Server',
subtitle:
Expand Down
23 changes: 23 additions & 0 deletions store/settings/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Vue from 'vue'
import { TextileError } from '../textile/types'
import { db } from '~/libraries/SatelliteDB/SatelliteDB'
import TextileManager from '~/libraries/Textile/TextileManager'
import { UserInfoManager } from '~/libraries/Textile/UserManager'
import { SettingsError, SettingsState } from '~/store/settings/types'
import { ActionsArguments } from '~/types/store/store'

Expand All @@ -13,4 +17,23 @@ export default {
throw new Error(SettingsError.DATABASE_NOT_CLEARED)
}
},
async setConsentScan(
{ commit }: ActionsArguments<SettingsState>,
consentScan: boolean,
) {
try {
commit('setConsentScan', consentScan)

const $TextileManager: TextileManager = Vue.prototype.$TextileManager

if (!$TextileManager.userInfoManager) {
throw new Error(TextileError.USERINFO_MANAGER_NOT_FOUND)
}
const $UserInfoManager: UserInfoManager = $TextileManager.userInfoManager
$UserInfoManager.setConsent({
consentScan,
consentDate: Date.now(),
})
} catch (e) {}
},
}
3 changes: 3 additions & 0 deletions store/settings/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const mutations = {
// will not react to deep values
state.embeddedLinks = value
},
setConsentScan(state: SettingsState, value: boolean) {
state.consentScan = value
},
displayCurrentActivity(state: SettingsState, value: boolean) {
state.displayCurrentActivity = value
},
Expand Down
1 change: 1 addition & 0 deletions store/settings/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const InitialSettingsState = (): SettingsState => ({
userDeniedAudioAccess: false,
keybinds: KeybindTypes,
embeddedLinks: true,
consentScan: false,
displayCurrentActivity: true,
removeState: false,
serverType: 'satellite',
Expand Down
1 change: 1 addition & 0 deletions store/settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface SettingsState {
userDeniedAudioAccess: boolean
keybinds: object
embeddedLinks: boolean
consentScan: boolean
displayCurrentActivity: boolean
timezone: string
removeState: boolean
Expand Down
8 changes: 8 additions & 0 deletions store/textile/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export default {
const $FileSystem: FilSystem = Vue.prototype.$FileSystem
await $FileSystem.import(fsExport)
}
const consentData = await $TextileManager.userInfoManager?.getConsentData()
if (consentData) {
/* Log CSAM Consent Data for future ticket as Hogan requested */
Vue.prototype.$Logger.log('CSAM Consent Data', 'CSAM', consentData)
commit('settings/setConsentScan', consentData.consent_scan, {
root: true,
})
}
return textilePublicKey
},
/**
Expand Down
1 change: 1 addition & 0 deletions store/textile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ export enum TextileError {
MAILBOX_MANAGER_NOT_FOUND = 'errors.textile.mailbox_manager_not_found',
MAILBOX_MANAGER_NOT_INITIALIZED = 'errors.textile.mailbox_manager_not_initialized',
METADATA_MANAGER_NOT_FOUND = 'errors.textile.metadata_manager_not_found',
USERINFO_MANAGER_NOT_FOUND = 'errors.textile.userinfo_manager_not_found',
}
7 changes: 7 additions & 0 deletions types/textile/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface UserdataFromThread {
_id: string
created_at: number
user_address: string
consent_scan: boolean
consent_date: number
}

0 comments on commit 3dec325

Please sign in to comment.